REUSEADDR,服务器连接多个客户端,点对点聊天程序

1. REUSEADDR


服务器端尽可能使用REUSEADDR 选项
在绑定地址端口之前尽可能调用setsocktopt()来设置REUSEADDR套接字选项

使用了REUSEADDR选项后,可以使得不必等待TIME_WAIT状态消失就可以重启服务器

//设置REUSEADDR 选项
    int on = 1;
    run = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if(run < 0)
    {
  	printf("error setsokopt \n");
        return -5;
    }  


 
2.服务器连接多个客户端
 

使用多进程进行处理,用子进程处理链接
                   父进程继续处理accept();从完成链接队列中再获取一个
服务器端有两个套接字,一个已连接套接口,主要是用来通信的
                     一个监听调节口,主要实现三次握手的过程

客户端只有一个套接字,链接套接字

//服务器端函数
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>

void do_service(int conn)
{
    //实现回射通信
    char recvbuf[1024];
    while(1)
    {   
        memset(recvbuf,0,sizeof(recvbuf));
	int ret=0;
	//接受客户端的请求,read()失败返回-1
        ret = read(conn,recvbuf,sizeof(recvbuf)); //返回实际接受到的字节数
        if(ret == 0)  //说明客户端关闭了
        {
            printf("client close \n");
            break;
        }
        else if(ret == -1) //-1表示失败了
        {
           printf("error read() \n");
           break;
        }

	//对请求进行处理
	printf("%s",recvbuf);
	//对客户端进行数据应答
        write(conn,recvbuf,ret);
    }
}
int main()
{
    int listenfd;
    int run;
	
    //首先调用socket()函数创建套接字
    listenfd =socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); //在LINUX 下可以用man socket 查询该函数的具体描述
    if(listenfd < 0)
    {
	printf("ERROR\n");
	return -1;
    }
    struct sockaddr_in servaddr;           //IPV4的地址结构
    memset(&servaddr,0,sizeof(servaddr));  
    // 初始化地址
    servaddr.sin_family = AF_INET;         //地址的家族
    servaddr.sin_port = htons(5188);       //端口号,2个字节,这里需要的是网络字节序(大端),需要将5188转换为网络字节序
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置地址,INADDR_ANY表示本地的任意地址
   
    //设置REUSEADDR 选项
    int on = 1;
    run = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if(run < 0)
    {
  	printf("error setsokopt \n");
        return -5;
    }  
  
    //第二步为套接字绑定一个本地地址
    run=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    if(run  < 0)
    {
	printf("error\n");
        return -2;
    }
	
    //第三步:使套接字处于监听状态
    run = listen(listenfd,SOMAXCONN);
    if(run < 0)
    {
       printf("error\n");
       return -3;
    }
    struct sockaddr_in peeraddr;         //定义对方的地址
    socklen_t peerlen =sizeof(peeraddr); //定义对方的地址长度大小,注意需要有初始值,负责 accept()会失败
    int conn;
    pid_t pid;	
    //第四步:从已完成连接队列中返回第一个连接,如果没有连接过来一直处于阻塞状态
    while(1)
    {
    	conn = accept(listenfd,(struct sockaddr *)&peeraddr,&peerlen);
    	if(conn < 0)
    	{
		printf("error\n");
		return  -4;
    	}

     	//如果连接成功可以将对方的连接地址打印出来
    	printf("ip= %s ,port=%d \n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
	
 	//创建进程        
	pid = fork();
	if(pid == -1) // 失败
	{
	   printf("error fork()\n");
	   return -1;
	}
 	if(pid == 0) //fork()为子进程返回0
        {
            close(listenfd);  //子进程不需要处理监听,只需要处理链接
            do_service(conn); //服务器相应函数
            exit(EXIT_SUCCESS);
	}
	else         //为父进程返回非负整数
        {
	    close(conn);     //父进程不需要处理链接,关闭链接套接口
  	}

    }	
    return 0;
}

3. 点对点聊天程序 
 
在服务器端/客户端创建一个线程专门用来从键盘上获取字符,发送到客户端
                另外一个进程专门用来进行接受字符

//服务器端函数
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#include<signal.h>

void handler(int sig){
   printf("recv a sig = %d\n",sig);
   printf("服务器端子进程退出\n");
   exit(0);
}
int main()
{
    int listenfd;
    int run;
     extern int errno;	
    //首先调用socket()函数创建套接字
    listenfd =socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); //在LINUX 下可以用man socket 查询该函数的具体描述
    if(listenfd < 0)
    {
	printf("ERROR\n");
	return -1;
    }
    struct sockaddr_in servaddr;           //IPV4的地址结构
    memset(&servaddr,0,sizeof(servaddr));  
    // 初始化地址
    servaddr.sin_family = AF_INET;         //地址的家族
    servaddr.sin_port = htons(5188);       //端口号,2个字节,这里需要的是网络字节序(大端),需要将5188转换为网络字节序
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置地址,INADDR_ANY表示本地的任意地址
   
    //设置REUSEADDR 选项
    int on = 1;
    run = setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if(run < 0)
    {
  	printf("error setsokopt \n");
        return -5;
    }  
  
    //第二步为套接字绑定一个本地地址
    run=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
    if(run  < 0)
    {
	printf("error\n");
        return -2;
    }
	
    //第三步:使套接字处于监听状态
    run = listen(listenfd,SOMAXCONN);
    if(run < 0)
    {
       printf("error\n");
       return -3;
    }
    struct sockaddr_in peeraddr;         //定义对方的地址
    socklen_t peerlen =sizeof(peeraddr); //定义对方的地址长度大小,注意需要有初始值,负责 accept()会失败
    int conn;
    pid_t pid;	
    //第四步:从已完成连接队列中返回第一个连接,如果没有连接过来一直处于阻塞状态
  
    conn = accept(listenfd,(struct sockaddr *)&peeraddr,&peerlen);
    if(conn < 0)
    {
	printf("error\n");
	return  -4;
    }

    //如果连接成功可以将对方的连接地址打印出来
    printf("ip= %s ,port=%d \n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
	
    //创建进程        
    pid = fork();
    if(pid == -1) // 失败
    {
        printf("error fork()\n");
	return -1;
    }
    if(pid == 0) //fork()为子进程返回0,专门用来发送数据
    {   
        close(listenfd);  //子进程不需要处理监听,只需要处理链接  
        int writeerro;
        char sendbuff[1024];
        signal(SIGUSR1,handler); //信号处理函数,当收到这个信号时,调用信号处理函数
        while( fgets(sendbuff,sizeof(sendbuff),stdin)!=NULL)
        {    
             writeerro=write(conn,sendbuff,strlen(sendbuff));
             memset(sendbuff,0,sizeof(sendbuff));
        }
    }
    else         //为父进程返回非负整数,专门用来接受数据
    {
        char recvbuff[1024];
        while(1)
        {
            memset(recvbuff,0,sizeof(recvbuff));
            int ret = read(conn,recvbuff,sizeof(recvbuff));
            if(ret == -1)
            {
                printf("error read \n");
                break;
            }
            else if(ret == 0)
            {
                printf("peer close \n");
                break;

            }
            fputs(recvbuff,stdout);
        }
        kill(pid,SIGUSR1);//当父进程退出时向子进程发送SIGUSR1信号
        printf("服务器端父进程退出\n");
        close(conn);
        exit(0);
    }	
    return 0;
}

//客户端程序
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
 
int main()
{
    int sock;
    int error=0;
    //第一步 创建客户端套接字
    sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock < 0)
    {
    printf("error socket();\n");
        return -1;
    }
    //设置连接地址
    struct sockaddr_in client;
    memset(&client,0,sizeof(client));
    client.sin_family=AF_INET;
    client.sin_port=htons(5188);
    client.sin_addr.s_addr =inet_addr("127.0.0.1");
    //客户端发起连接
    error=connect(sock,(struct sockaddr *)&client,sizeof(client));
    if(error<0)
    {
        printf("error connect();%d\n",error);
        return -2;
    }
    pid_t pid;
    pid = fork();
    if(pid == -1)
    {
        printf("error fork \n");
        return -1;
    }
    else if(pid == 0) //子进程返回0,专门用来接收数据
    {    
        char recvbuff[1024];
        while(1)
        {
            memset(recvbuff,0,sizeof(recvbuff));
            int ret = read(sock,recvbuff,sizeof(recvbuff));
            if(ret == -1)
            {
                printf("error read \n");
            }
            else if(ret == 0)
            {
                printf("peer close \n");
                break;
            }
            fputs(recvbuff,stdout);
        }
        printf("客户端子进程退出\n");         
        exit(0);
    }
    else //为父进程返回非负整数,专门用来发送数据  
    {
        char sendbuff[1024];
        while( fgets(sendbuff,sizeof(sendbuff),stdin)!=NULL)
        {
             write(sock,sendbuff,strlen(sendbuff));
             memset(sendbuff,0,sizeof(sendbuff));
        }
        close(sock);
        printf("客户端父进程退出\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/b903299114/article/details/78828242