多进程方式
实现思路
每当accept一个连接,就会创建一个子进程来处理该连接并进行通信。
服务器
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
if ( -1 == bind(sock,(struct sockaddr *)&ser,sizeof(ser) ) ){
perror("bind error\n");
}
listen(sock,10);
printf("wait listen\n");
int fd;
while(1){
struct sockaddr_in cli;
int len = sizeof( cli );
fd = accept(sock,(struct sockaddr *)&cli,&len);
printf("new client connected\n");
pid_t pid = fork();
if(pid == 0)
{
break;
}
}
//read/write
while(1){
char buf[20] = {0};
read(fd, buf , 20);
printf(" [%d] send %s \n",fd,buf);
write(fd,"send successed",20);
}
close(sock);
}
客户端
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
/* int connect (int sockfd , const struct sockaddr *addr,socklen_t len);*/
if ( -1 == bind(sock,(struct sockaddr *)&ser,sizeof(ser) ) ){
perror("bind error\n");
}
//write/read 循环写
while(1)
{
sleep(1);
write( sock , "HAVE A GOOD DAY!" ,20);
char buf[20] = {0};
read( sock , buf , 20);
printf("%s \n",buf);
}
close(sock);
}
多线程方式
实现思路
为每个新加入的客户端创建一个线程,在线程函数内执行自己的输入输出操作。
服务端
void * routine(void * arg)
{
int fd =(int)arg;
while(1){
sleep(1);
write(fd,"hello cli",9);
char buf[20] = {0};
read(fd,buf,sizeof(buf));
printf("fd = %d , buf = %s \n",fd,buf);
}
}
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sock,(struct sockaddr *)&ser,sizeof(ser));
if ( ret < 0){
perror("bind error\n");
}
listen(sock,10);
printf("wait listen\n");
int fd;
while(1){
struct sockaddr_in cli;
int len = sizeof( cli );
int fd = accept(sock,(struct sockaddr *)&cli,&len);
printf("new client connected\n");
pthread_t tid;
pthread_create( &tid ,NULL,routine,(void *)fd);
//不能使用join,因为join会将主函数阻塞
pthread_detach( tid );
}
close(sock);
}
客户端
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
/* int connect (int sockfd , const struct sockaddr *addr,socklen_t len);*/
int ret = connect( sock,(struct sockaddr *)&ser,sizeof(ser));
if(ret < 0){
perror("connect error\n");
exit( -1 );
}
//read/write
while(1)
{
char buf[10] = {0};
read( sock , buf ,sizeof(buf));
printf("receive:%s \n",buf);
write(sock,"hello cli",9);
}
close(sock);
}
select方式
实现思路
select系统调用同时监视用多个文件句柄,程序会阻塞在select函数这里,当有一个或多个句柄发生变化时就会执行相应的操作,最大只能监视1024个客户端。
select函数
int select( int nfds,fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
int nfds 描述符内最大值加1
fd_set *readfds 读 不需要时传NULL
fd_set *writefds 写 不需要时传NULL
fd_set *exceptfds 异常 不需要时传NULL
struct timeval *timeout 等待时间 : 0为不等
>0等待几秒或几毫秒
NULL为死等
因为readfds、writefds、exceptfds不仅是输入也是输出,所以在select时需要注意当运行过后,值是否会改变,是否需要重新赋值
返回值
0 :没有就位,且超时
-1:有错
>0:有人准备好
fd_set 是需要监测的fd的集合,包含很多个fd
fd_set操作函数:
void FD_CLR (int fd,fd_set *set); 从集合里面移出
int FD_ISSET (int fd, fd_set *set); 是否在集合中
void FD_SET (int fd, fd_set *set); 放入集合
void FD_ZERO (fd_set *set); 清空集合
服务端
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sock,(struct sockaddr *)&ser,sizeof(ser));
if ( ret < 0){
perror("bind error\n");
}
listen(sock,5); //int listen(int sockfd,int backlog最大长度);
printf("wait listen\n");
int fd[5]; //存放连接到服务器的客户端的描述符
int idx = -1; //存放新连接的下标
fd_set ofds; //存放需要监测的描述符
FD_SET( sock,&ofds); //将客户端也放入ofds中监测,监测是否有人请求连接
int max_fd = sock; //存放最大描述符
while(1){
fd_set rfds = ofds;
int ret = select(max_fd+1,&rfds,NULL,NULL,NULL);
if ( ret == -1 ){
perror("select error\n");
}else {
if (FD_ISSET(sock,&rfds)){ //有人请求连接
idx++;
struct sockaddr_in cli;
int len = sizeof( cli );
fd[idx] = accept(sock,(struct sockaddr *)&cli,&len); //接受连接
printf("connect success\n");
FD_SET( fd[idx],&ofds); //将新连接存入句柄中监测
max_fd = (fd[idx] > max_fd)?fd[idx]:max_fd; //检测最大描述符是否需要改变
}else{
for(int i = 0; i <= idx;i++) //检测每个已存入的连接是否发生变化
{
char buf[20] = {0};
if( FD_ISSET(fd[i],&rfds)){
read( fd[i] , buf ,sizeof(buf));
printf("fd[ %d ] receive %s", fd[i] , buf );
write(fd[i],"send successed",20);
}
}
}
}
}
close(sock);
}
客户端
int main()
{
int sock = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons( 8899 );
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect( sock,(struct sockaddr *)&ser,sizeof(ser));
if(ret < 0){
perror("connect error\n");
exit( -1 );
}
fd_set ofds;
FD_SET(0,&ofds); //键盘输入
FD_SET(sock,&ofds); //传输数据
while(1){
//因为select中rfds不仅是输入也是输出,当执行完一次后rfdsd的值会改变,所以需要不断将初始值ofds赋给rfds
fd_set rfds = ofds;
int ret = select( sock+1,&rfds,NULL,NULL,NULL);
if ( ret == -1 ){
perror("select error\n");
}else{
char buf[20] = {0};
if( FD_ISSET(sock,&rfds)){ //检测到有数据传入
read( sock , buf ,sizeof(buf));
printf("receive:%s \n",buf);
}
if( FD_ISSET(0,&rfds)){ //检测到有键盘输入
int ret = read( 0 , buf ,sizeof(buf)); //标准输入
write(sock,buf,ret);
}
}
}
close(sock);
}
poll方式
实现思路
和select的本质是一样的,但没有最大文件描述符限制
poll函数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd *fds
struct pollfd{
int fd; //文件描述符
short events; //监测事件
short revents; //事件监测结果
};
events、revents:
POLLIN:普通或带外优先数据可读
POLLHUP:程序挂起
nfds_t nfds //需要监测的文件数量
int timeout //毫秒级等待
0:立即返回
>0:等待xx毫秒
-1:阻塞等
服务端
#define SA struct sockaddr
int main()
{
int sfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in s;
s.sin_family = AF_INET;
s.sin_port = htons(8899);
s.sin_addr.s_addr = inet_addr("127.0.0.1");
int len = sizeof(s);
if( -1 == bind(sfd,(SA *)&s,len)){
perror("bind error\n");
return -1;
}
listen(sfd,10);
printf("wait connect\n");
struct pollfd p[10];
p[0].fd = sfd;
p[0].events = POLLIN;
int num = 1;
while(1){
int ret = poll(p,10,-1);
if(ret == 0){
printf("timeout\n");
}
else if(ret == -1){
perror("select error\n");
}else{
if(p[0].revents & POLLIN){
printf("0x%x \n",p[0].revents);
int cfd = accept(sfd,(SA *)&s,&len);
p[num].fd = cfd;
p[num].events = POLLIN | POLLHUP;
printf("new client connected total %d\n",num);
num++;
}
int i = 1;
for( i = 1; i<num;i++){
if(p[i].revents & POLLHUP){ //客户端关闭,关闭连接
close(p[i].fd);
p[i].fd = p[num].fd;
num--;
printf("a client left, rest %d connect \n",num-1);
}else if(p[i].revents & POLLIN){ //客户端传来数据
char buf[100] = {0};
read(p[i].fd,buf,sizeof(buf));
printf("fd [%d] receive: %s \n",p[i].fd,buf);
write(p[i].fd,"write successed",20);
}
}
}
}
close(sfd);
return 0;
}
客户端
#define SA struct sockaddr
int main()
{
int sfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in s;
s.sin_family = AF_INET;
s.sin_port = htons(8899);
s.sin_addr.s_addr = inet_addr("127.0.0.1");
int len = sizeof(s);
if( -1 == connect(sfd,(SA *)&s,len)){
perror("connect error\n");
return -1;
}
printf("connect successed\n");
struct pollfd p[2];
p[0].fd = sfd;
p[0].events = POLLIN | POLLHUP;
p[1].fd = 0;
p[1].events = POLLIN | POLLHUP;
while(1){
int ret = poll(p,2,-1);
if(ret == 0){
printf("timeout\n");
}
else if(ret == -1){
perror("select error\n");
}else{
char buf[100] = {0};
if(p[1].revents & POLLIN){
read(0,buf,100);
write(sfd,buf,strlen(buf));
}
if(p[0].revents & POLLHUP){
printf("server shut down \n");
close( p[0].fd );
return 0;
}else if(p[0].revents & POLLIN){
memset(buf,100,0);
read(sfd,buf,sizeof(buf));
printf("%s \n",buf);
}
}
}
return 0;
}