select版本的回显服务,初期的bug是,客户端可以连接上服务器,但是客户端发送字符串过来以后,服务器的select却阻塞在那里,死活没反映。。。gdb调试查看了fd_set类型的值,也发现的确是有两个值的叠加的——一个是监听套接字,一个是刚刚连接上的客户套接字。这就奇怪了!直到看到 http://bbs.csdn.net/topics/230024080 上别人的提问,最后他说:解决了,原因是我的socket集中混进了一个 错误的socket,select一直报10038的错误,
于是我也开始找这方面的错误,但是找不到,最后与标准代码对比,才发现错误的原因,是select第一个参数设置错误。
错误代码:服务器ser.c
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <string.h> #include <errno.h> #include <fcntl.h> #define CONN_MAX 10000 #define cerror(str) do{perror(str); exit(EXIT_FAILURE);}while(0) int sock_init_ser(int is_any, const char *addr, int port, int backlog); void do_client_sock(int *pcd); int conn_fd[CONN_MAX]; int conn_n; // 当前数组中最大可用的元素序号+1,检查时,>=conn_n的元素可以跳过 int fd_max; /* * MAIN * 回显服务的服务器端 * 使用select实现 */ int main(int argc, char * argv[]) { int sd; fd_set fs; int i; if(argc<3) { printf("Usage: argv[0] port backlog [ipaddr]\n"); exit(0); } if (argc>=4) { sd=sock_init_ser(0,argv[3],atoi(argv[1]), atoi(argv[2])); }else { sd=sock_init_ser(1,NULL,atoi(argv[1]), atoi(argv[2])); } for(i=0; i<CONN_MAX; i++) // init to -1 { conn_fd[i]=-1; } conn_n=0; fd_max=sd; while(1) { int sr; FD_ZERO(&fs); FD_SET(sd, &fs); fd_max=sd; for (i=0; i<conn_n; i++) { if (conn_fd[i]!=-1) { // continue; FD_SET(conn_fd[i], &fs); if(i>fd_max) { fd_max=i;//conn_fd[i]; // 不是fd_max=i !!! } } } sr = select(fd_max+1, &fs, NULL, NULL, NULL); // select if (sr<0) // select error { perror("select"); continue; } if(sr==0) // select timeout { continue; } for (i=0; i<conn_n; i++) { if(conn_fd[i]!=-1 && FD_ISSET(conn_fd[i], &fs)) do_client_sock(conn_fd+i); // echo back } // listen socket if (FD_ISSET(sd, &fs)) { int cd=accept(sd, NULL, NULL); if(cd<0) { if(errno == EINTR) { continue; } else { perror("accept"); continue; } } // for(i=0; i<conn_n+1; i++) // or i<CONN_MAX { if(conn_fd[i]==-1) { conn_fd[i]=cd; printf("add a new client\n"); break; } } if(i>=CONN_MAX-1) { close(cd); conn_fd[i]=-1; printf("conn_fd full\n"); continue; } if(i>=conn_n) conn_n=i+1; printf("conn_n= %d\n",conn_n); } } close(sd); return 0; } /* * sock_init_ser * is_any为0时,需要指定IP地址,为非零时,IP地址字段被忽略,使用INADDR_ANY * port为端口号,backlog为监听个数 */ int sock_init_ser(int is_any, const char *addr, int port, int backlog) { int sd; struct sockaddr_in sin; sd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sd<0) { cerror("socket"); } memset(&sin, 0, sizeof(sin) ); sin.sin_family = AF_INET; sin.sin_port = htons(port); if (is_any) { sin.sin_addr.s_addr = htonl(INADDR_ANY); }else { inet_pton(AF_INET, addr, &(sin.sin_addr)); } if (bind(sd, (struct sockaddr*)&sin,sizeof(sin))<0) { cerror("bind"); } if(listen(sd,backlog)<0) cerror("listen"); printf("server open addr: %d\n", ntohs(sin.sin_port)); return sd; } /* * do_client_sock * 监听客户套接子,有消息到来则read,然后打印到标准输出, * 并发回给客户,同时需要处理关闭和出错,此时需要将客户套接子从数组里删除。 */ void do_client_sock(int *pcd) { char buf[256]; int nr = read(*pcd, buf, 256-1); if(nr<0) { perror("read\n"); close(*pcd); *pcd=-1; printf("del a client\n"); return; } if (nr==0) { close(*pcd); *pcd=-1; printf("del a client\n"); return; } printf("recv: %s\n", buf); write(*pcd, buf, 256-1); }客户端:
#include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <string.h> #include <errno.h> #include <fcntl.h> #define cerror(str) do{perror(str); exit(EXIT_FAILURE);}while(0) int sock_init_client( const char *addr, int port); void do_client_sock(int cd); /* * MAIN * 回显服务的客户端程序,使用格式形如client 5778 192.168.1.105 * 连接以后,发送some test string by %d, seq: %d字符串给服务器,第一个整数是进程id * 第二个整数是序号,从1开始递增。完成一次回显以后,休眠10s,随后开始下一个。 */ int main(int argc, char * argv[]) { int cd; if(argc<3) { printf("Usage: argv[0] port ipaddr"); } cd= sock_init_client(argv[2], atoi(argv[1])) ; do_client_sock(cd); return 0; } /* * do_client_sock * 连接完成以后执行循环回显 */ void do_client_sock(int cd) { while(1) { char buf[256]; static int seqnum=1; int nr; snprintf(buf, sizeof(buf), "some test string by %d, seq: %d",getpid(), seqnum++); nr=write(cd, buf, strlen(buf)); if(nr!=strlen(buf)) printf("write error\n"); nr = read(cd, buf, 256-1); if(nr<0) { perror("read\n"); close(cd); exit(1); } if (nr==0) { close(cd); exit(1); } buf[strlen(buf)]='\0'; printf("echo back: %s\n", buf); sleep(10); } } /* * sock_init_client * 给定IP地址和端口号,执行连接 */ int sock_init_client( const char *addr, int port) { int cd; struct sockaddr_in sin; cd=socket(AF_INET, SOCK_STREAM, 0); if(cd<0) { cerror("socket"); } memset(&sin, 0, sizeof(sin) ); sin.sin_family = AF_INET; sin.sin_port = htons(port); inet_pton(AF_INET, addr, &(sin.sin_addr)); if (connect(cd, (struct sockaddr*)&sin, sizeof(sin) )<0) { cerror("connect"); } return cd; }
for (i=0; i<conn_n; i++) { if (conn_fd[i]!=-1) { // continue; FD_SET(conn_fd[i], &fs); if(i>fd_max) { fd_max=i;//conn_fd[i]; // 不是fd_max=i !!! } } }套接字描述是一个个分配的,监听套接字先分配,accept进来的客户套接字后分配,所以客户套接字会比监听套接字大。而conn_n在第一次accept进一个或若干个客户后,值为1或2,3.。。。因此,用i去于fd_max比,是不正确的。假如只有一个客户,那么循环一次就退出,i=0肯定小于fd_max,因此fd_max最后只囊括了监听套接字,客户套接字忽略了。
因此应该改为
for (i=0; i<conn_n; i++) { if (conn_fd[i]!=-1) { // continue; FD_SET(conn_fd[i], &fs); if(conn_fd[i]>fd_max) { fd_max=conn_fd[i]; // 不是fd_max=i !!! } } }
运行:
服务器:
administrator@ubuntu:~/mytest$ ./ser 8887 5 192.168.1.105
server open addr: 8887
add a new client
conn_n= 1
recv: some test string by 5757, seq: 1
recv: some test string by 5757, seq: 2
recv: some test string by 5757, seq: 3
recv: some test string by 5757, seq: 4
recv: some test string by 5757, seq: 5
recv: some test string by 5757, seq: 6
客户:
administrator@ubuntu:~/mytest$ ./client 8887 192.168.1.105
echo back: some test string by 5757, seq: 1
echo back: some test string by 5757, seq: 2
echo back: some test string by 5757, seq: 3
echo back: some test string by 5757, seq: 4
echo back: some test string by 5757, seq: 5
echo back: some test string by 5757, seq: 6