Linux网络编程--服务器客户端(TCP实现)


Linux下的一个服务器客户端的小程序,基于TCP的实现;服务器可以同时接受多个客户的接入,通过子进程处理客户请求,下面的例子中,服务器只将客户的IP和端口以及发送的信息显示,然后原样的将客户发送的信息发送给客户。客户端仅仅是输入信息以及显示收到的信息。

TCP通信的模式如下图,比较固定,对着图编代码就可以了:

Linux网络编程--服务器客户端(TCP实现)

服务器的main函数:

int main(int argc, char **argv)
{
 int listenfd, connfd;
 pid_t childpid;
 socklen_t clilen;
 struct sockaddr_in cliaddr, servaddr; //IPv4 address
 /*socket*/
 listenfd = socket(AF_INET, SOCK_STREAM, 0);//创建一个TCP的socket
 if (-1 == listenfd) {
  perror("socket erro.");
  return -1;
 }
 /*bind*/
 //首先初始化server的IP地址和端口,然后再与刚刚创建的socket绑定
 bzero(&servaddr, sizeof(servaddr));
 servaddr.sin_family = AF_INET;//设置协议簇
 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定本机的网卡
 servaddr.sin_port = htons(1234);//绑定端口号,端口号可以随便取,大于1024就可以了
 if (-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
  perror("bind error.");
  return -1;
 }
 /*listen*/
 //到这里已经有了一个绑定了IP地址和端口号的socket了,但是这个socket是个主动的socket,
 //而作为server需要的是一个等待别的接入的被动的socket,所以得调用listen将这个socket设置为监听状态
 //第二个参数表示服务器正在处理客户接入时的等待队列长度。
 if (-1 == listen(listenfd, 10)) {
  perror("listen error.");
  return -1;
 }
 while (1) {
  clilen = sizeof(cliaddr);
  //调用accept等待客户的接入,同时accept会用第二个参数返回客户的IP地址,
  //通过第三个参数返回IP地址的实际大小,同时这个参数也是个值-结构参数,也就是
  //在传递这个参数的时候,先给这个参数一个初始的值,然后函数中会根据具体的情况修改这个值,
  //所以这里传递的是指针。
  //当客户接入后,将返回一个成功和客服连接的socket描述符,通过读写这个socket即可实现和客户的通信了
  connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
  if (-1 == connfd) {
   if (EINTR == errno)
    continue;
   perror("accept error.");
   return -1;
  }
  //通过fock创建子进程来处理客户请求,这里只是显示客户的IP地址、端口号和发送的文字,
  //并将客户发送的文字回传给客户。
  if (0 == (childpid=fork())) {//fock返回0 说明是子进程
   //这里并没有关闭服务器的监听socket,只是将其引用计数减一,因为fork出来的子进程对父进程做了拷贝,
   //所以这个监听socket的引用计数将由1变成2,而内核只有在一个socket的引用计数变为0才回去关闭它
   close(listenfd);
   //通过和客户连接的socket和客户通信     
   str_echo(connfd, cliaddr);
   return 0;
  }
  //父进程将和客户连接的socket的引用计数减一,同样并没有关闭这个socket
  close(connfd);
 }
 return 0;
}

服务器处理客户请求:

#define BSIZE 100
void str_echo(int sockfd, struct sockaddr_in cliaddr)
{
 ssize_t n;
 char buf[BSIZE];
 
 while ((n=read(sockfd, buf, BSIZE))) {//读取客户发送的信息
  buf[n] = '\0';
  printf("IP: %s, PORT: %d: %s\n", \
    inet_ntoa(cliaddr), ntohs(cliaddr.sin_port), buf); //输出信息
  n_write(sockfd, buf, n);//将受到的信息发送回客户,n_write的实现下面给出
 }
}

客户端程序相对简单:

int main(int argc, char **argv)
{
 int sockfd;
 struct sockaddr_in servaddr;
 
 if (2 != argc) {
  printf("usage: ./client 127.0.0.1");//
  return -1;
 }
 /*socket*/
 sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建一个socket
 if (-1 == sockfd) {
  perror("socket error.");
  return -1;
 }
 /*connect*/
 //首先初始化要连接的服务器的IP地址和端口号,然后调用connect去连接这个服务器
 bzero(&servaddr, sizeof(servaddr));
 servaddr.sin_family = AF_INET;
 servaddr.sin_port = htons(1234);
 inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
 if (-1 == connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
  perror("connect error.");
  return -1;
 }
 //连接成功后就可以通过这个socket来和服务器通信了
 str_cli(stdin, sockfd);

 return 0;
}

n_write 和 readline

/*write n bytes to fd*/
ssize_t n_write (int fd, void *buf, size_t n)
{
 size_t nleft = n;
 ssize_t nwriten;
 char *bufp = buf;

 while (nleft > 0) {
  if ((nwriten = write (fd, bufp, nleft)) <= 0) {
   if (EINTR == errno)
    nwriten = 0;
   else
    return -1;
  }

  nleft -= nwriten;
  bufp += nwriten;
 }

 return n;
}

/*read line from fd*/
ssize_t readline (int fd, void *buf, size_t maxlen)
{
 ssize_t n, rc;
 char c, *bufp;

 bufp = buf;
 for (n = 1; n < maxlen; n ++) {
again:
  if (1 == (rc = read (fd, &c, 1))) {
   *bufp ++ = c;
   if ('\n' == c)
    break;  /*newline is stored*/
  } else if (rc == 0) {
   *bufp = 0;
   return (n - 1);  /*EOF, n-1 bytes were read*/
  } else {
   if (EINTR == errno) /*interrupt*/
    goto again;
   return -1;  /*Erro, set the errno by read ()*/
  }
 }
 *bufp = 0;
 return n;
}

运行结果:

Linux网络编程--服务器客户端(TCP实现)

因为客户端没有指定IP地址和端口,所以其IP和端口都是内核随机分配的。

相关内容