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;
}
错误的地方在ser.c:
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