继续贴《unix网络编程》上的示例代码。这次是一个反射程序,反射是客户端讲用户输入的文本发送到服务器端,服务器端读取客户端发过来的文本消息,然后原封不动的把文本消息返回给客户端。使用tcp协议连接客户端和服务端,我已经在我的阿里云服务器上测试过了,能够完美运行。
首先是头文件wrap.h,在该头文件中,声明了封装部分网络编程套接字api的包裹函数,以及某些宏定义。
1 #ifndef WRAP_H_ 2 #define WRAP_H_ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <unistd.h> 7 #include <arpa/inet.h> 8 #include <sys/socket.h> 9 #include <netinet/in.h> 10 #include <sys/types.h> 11 #include <errno.h> 12 #include <string.h> 13 #include <strings.h> 14 #include <signal.h> 15 16 #define SOCKET_ERROR 1 17 #define BIND_ERROR 2 18 #define LISTEN_ERROR 3 19 #define CONNECT_ERROR 4 20 #define ACCEPT_ERROR 5 21 #define READ_ERR0R 6 22 #define SIGNAL_ERROR 7 23 #define PROCESS_ERROR 8 24 25 #define MAXLINE 4096 26 #define SER_PORT 9375 27 28 /* 29 *All the function is possible in unix network programing. 30 *On success,0 is returned; 31 *or on error,number above 0 is returned; 32 */ 33 int Socket (int domain,int type,int protocol); 34 int Bind (int sockfd,const struct sockaddr *addr,socklen_t addrlen); 35 int Listen (int sockfd,int backlog); 36 int Connect (int sockfd,const struct sockaddr *addr,socklen_t addrlen); 37 38 /*read n bytes from a file descriptor*/ 39 ssize_t readn (int fd,void *vptr,size_t n); 40 /*write n bytes into a file descriptor*/ 41 ssize_t writen (int fd,void *vptr,size_t n); 42 /*read a line*/ 43 static int read_cnt; 44 static char *read_ptr; 45 static char read_buf[MAXLINE]; 46 static ssize_t my_read (int fd,char *ptr); 47 ssize_t readline (int fd,void *vptr,size_t maxlen); 48 ssize_t readlinebuf (void **vptrptr); 49 50 /*for linux signal*/ 51 typedef void Sigfunc (int); 52 Sigfunc *Signal (int signo,Sigfunc *func); 53 54 /*handle signal SIGCHLD*/ 55 void sig_chld (int signo); 56 57 /*create child process*/ 58 pid_t Fork (); 59 60 #endif
接下来是这些函数的实现wrap.c:
1 #include "wrap.h" 2 3 int Socket (int domain,int type,int protocol) 4 { 5 int result = 0; 6 result = socket (domain,type,protocol); 7 if (result == -1) { 8 perror ("socket"); 9 exit (SOCKET_ERROR); 10 } 11 12 return result; 13 } 14 15 int Bind (int sockfd,const struct sockaddr *addr,socklen_t addrlen) 16 { 17 int result = 0; 18 result = bind (sockfd,addr,addrlen); 19 if (result == -1) { 20 perror ("bind"); 21 exit (BIND_ERROR); 22 } 23 24 return 0; 25 } 26 27 int Listen (int sockfd,int backlog) 28 { 29 int result = 0; 30 result = listen (sockfd,backlog); 31 if (result == -1) { 32 perror ("listen"); 33 exit (LISTEN_ERROR); 34 } 35 36 return 0; 37 } 38 39 int Connect (int sockfd,const struct sockaddr *addr,socklen_t addrlen) 40 { 41 int result = 0; 42 result = connect (sockfd,addr,addrlen); 43 if (result == -1) { 44 perror ("connect"); 45 exit (CONNECT_ERROR); 46 } 47 48 return 0; 49 } 50 51 /*read n bytes from a file descriptor*/ 52 ssize_t readn (int fd,void *vptr,size_t n) 53 { 54 size_t nleft; 55 ssize_t nread; 56 char *ptr; 57 58 ptr = vptr; 59 nleft = n; 60 /*read until n bytes*/ 61 while (nleft > 0) { 62 /*read a string*/ 63 if ((nread = read (fd,ptr,nleft)) < 0) { 64 if (errno == EINTR) { 65 /* 66 *The call was interrupted by a signal before any data was read 67 *and call it again. 68 */ 69 nread = 0; 70 } 71 else { 72 /*the call was interrupted by other signal,return -1*/ 73 return (-1); 74 } 75 } else if (nread == 0) { 76 break; /*read a EOF from file descriptor and read all data*/ 77 } 78 79 nleft = nleft - nread; 80 ptr += nread; 81 } 82 83 return (n-nleft); 84 } 85 86 /*write n bytes into a file descriptor*/ 87 ssize_t writen (int fd,void *vptr,size_t n) 88 { 89 size_t nleft; 90 ssize_t nwritten; 91 const char *ptr; 92 93 ptr = vptr; 94 nleft = n; 95 while (nleft > 0) { 96 if ((nwritten = write (fd,ptr,nleft)) <= 0) { 97 /*nothing written or an error occured*/ 98 if (nwritten < 0 && errno == EINTR) { 99 /* 100 *The call was interrupted by a signal before any data was read 101 *and call it again. 102 */ 103 nwritten = 0; 104 } else { 105 /*the call was interrupted by other signal,return -1*/ 106 return (-1); 107 } 108 } 109 110 nleft -= nwritten; 111 ptr += nwritten; 112 } 113 114 return (n); 115 } 116 117 static ssize_t my_read (int fd,char *ptr) 118 { 119 if (read_cnt <= 0) { 120 again: 121 if ((read_cnt = read (fd,read_buf,sizeof (read_buf))) < 0) { 122 if (errno == EINTR) { 123 /* 124 *The call was interrupted by a signal before any data was read 125 *and call it again. 126 */ 127 goto again; 128 } 129 /*the call was interrupted by other signal,return -1*/ 130 return (-1); 131 } else if (read_cnt == 0) { 132 /*nothing to read*/ 133 return 0; 134 } 135 read_ptr = read_buf; 136 } 137 138 read_cnt --; 139 *ptr = *read_ptr++; 140 return 1; 141 } 142 143 ssize_t readline (int fd,void *vptr,size_t maxlen) 144 { 145 ssize_t n,rc; 146 char c,*ptr; 147 148 ptr = vptr; 149 for (n = 1;n < maxlen;n ++) { 150 if ((rc = my_read (fd,&c)) == 1) { 151 *ptr++ = c; 152 if (c == '\n') 153 break; 154 } else if (rc == 0) { 155 *ptr = 0; 156 return (n - 1); 157 } else { 158 return (-1); 159 } 160 } 161 162 *ptr = 0; 163 return n; 164 } 165 166 ssize_t readlinebuf (void **vptrptr) 167 { 168 if (read_cnt) 169 *vptrptr = read_ptr; 170 return (read_cnt); 171 } 172 173 /*handle SIGCHLD*/ 174 void sig_chld (int signo) 175 { 176 pid_t pid; 177 int stat; 178 179 while ((pid = waitpid (-1,&stat,WNOHANG)) > 0) { 180 printf ("child %d terminated\n",pid); 181 } 182 183 return; 184 } 185 186 /*signal handle*/ 187 Sigfunc* Signal (int signo,Sigfunc *func) 188 { 189 struct sigaction act,oact; 190 act.sa_handler = func; 191 sigemptyset (&act.sa_mask); 192 act.sa_flags = 0; 193 194 if (signo == SIGALRM) 195 act.sa_flags |= SA_RESTART; 196 197 if (sigaction (signo,&act,&oact) < 0) { 198 exit (SIGNAL_ERROR); 199 } 200 201 return (oact.sa_handler); 202 } 203 204 pid_t Fork () 205 { 206 pid_t pid; 207 pid = fork (); 208 if (pid < 0) { 209 perror ("fork"); 210 exit (PROCESS_ERROR); 211 } else { 212 return pid; 213 } 214 }
服务端代码server.c:
1 #include "wrap.h" 2 3 #define SER_PORT 9375 4 5 extern void sig_chld (int signo); 6 void str_echo (int connfd); 7 8 int main () 9 { 10 int listenfd,connfd; 11 pid_t child; 12 socklen_t chilen; 13 struct sockaddr_in cliaddr,seraddr; 14 char ip[20]; 15 16 listenfd = Socket (AF_INET,SOCK_STREAM,0); 17 bzero (&cliaddr,sizeof (cliaddr)); 18 bzero (&seraddr,sizeof (seraddr)); 19 20 seraddr.sin_family = AF_INET; 21 seraddr.sin_port = htons (SER_PORT); 22 seraddr.sin_addr.s_addr = htonl (INADDR_ANY); 23 24 Bind (listenfd,(struct sockaddr*)&seraddr,sizeof (seraddr)); 25 Listen (listenfd,20); 26 Signal (SIGCHLD,sig_chld); 27 28 while (1) { 29 chilen = sizeof (cliaddr); 30 if ((connfd = accept (listenfd, 31 (struct sockaddr*) &cliaddr, 32 &chilen)) < 0) { 33 if (errno == EINTR) 34 continue; 35 else 36 perror ("accept"); 37 } else { 38 printf ("a client connected,"); 39 inet_ntop (AF_INET,&cliaddr.sin_addr,ip,chilen); 40 printf ("ip address:%s\n",ip); 41 ip[0] = '\0'; 42 } 43 44 if ((child = Fork ()) == 0) { 45 /*this is child process*/ 46 close (listenfd); 47 str_echo (connfd); 48 exit (0); 49 } 50 close (connfd); 51 } 52 53 return 0; 54 } 55 56 void str_echo (int connfd) 57 { 58 ssize_t n; 59 char buf[MAXLINE]; 60 again: 61 while ((n = read (connfd,buf,MAXLINE)) > 0) 62 writen (connfd,buf,n); 63 64 if (n < 0 && errno == EINTR) 65 goto again; 66 else if (n < 0) { 67 perror ("write"); 68 exit (1); 69 } 70 }
客户端代码client.c:
1 #include "wrap.h" 2 3 #define SER_PORT 9375 4 5 void str_cli (int connfd); 6 7 int main () 8 { 9 int sockfd; 10 char *ip = "115.28.75.192"; 11 struct sockaddr_in seraddr; 12 13 sockfd = Socket (AF_INET,SOCK_STREAM,0); 14 15 seraddr.sin_family = AF_INET; 16 seraddr.sin_port = htons (SER_PORT); 17 inet_pton (AF_INET,ip,&seraddr.sin_addr); 18 19 Connect (sockfd,(struct sockaddr*)&seraddr,sizeof (seraddr)); 20 str_cli (sockfd); 21 22 return 0; 23 } 24 25 void str_cli (int connfd) 26 { 27 char sendline[MAXLINE]; 28 char recvline[MAXLINE]; 29 30 while (fgets (sendline,MAXLINE,stdin) != NULL) { 31 writen (connfd,sendline,strlen (sendline)); 32 33 if (readline (connfd,recvline,MAXLINE) == 0) { 34 printf ("server closed too early"); 35 exit (0); 36 } 37 38 printf ("%s\n",recvline); 39 40 } 41 }
这段代码中,有几个需要注意的地方:
1.在服务端代码中,产生子进程之后,要关闭监听描述符(listenfd)。
2.在服务端代码中,父进程要关闭已连接描述符(connfd)。
3.子进程结束之后,会产生SIGCHLD信号,在父进程中捕获此信号,使用waitpid函数回收子进程的资源,以免出现僵尸进程。
4.accept等属于输入慢调用,会被信号中断,errno设置为EINTR,因此如果accept被中断,需要判断errno的值。
5.在readline函数实现中,使用了静态变量,这是线程不安全的函数,多线程编程中可能会存在问题。
代码中的问题:
在wrap.h中,我定义了SER_PORT的值,但是在client.c server.c 如果没有SER_PORT的宏定义时,在编译htons (SER_PORT)时,编译器就会报错,如下图。我在server.c中包含了wrap.h文件,为什么不能使用SER_PORT这个宏呢?
代码中有不完善的地方,请多指教。