zoukankan      html  css  js  c++  java
  • 08socket编程

    有个SO_REUSEADDR值得注意一下:

    服务器端尽可能使用SO_REUSEADDR
    在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。
    使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器。
    也就是如果你不这样子用的话会出现这样的问题:_
    就是用上节的服务器和客户端通信的时候,如果不设置这个选项的话,当服务器端先退出的话,在启动服务器端的话会失败。可以用netstat -an | grep TIME_WAIT来查看一下。
    具体设置:
    看截图一目了然,具体怎么用man一下就可以,养成好习惯。
     
     
    问题来了:上节的程序服务器端只可以接受一个客户端的连接。原因就在于当服务器端接收到一个客户端时它会进入到while中,那它就不能再accept了。
    这时我们可以用父子进程配合的方式来实现,一个服务器端可以接受多个客户端的连接。
     
    服务器端:
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    void do_service(int conn)
    {
    	char recvbuf[1024];
            while (1)
            {
                    memset(recvbuf, 0, sizeof(recvbuf));
                    int ret = read(conn, recvbuf, sizeof(recvbuf));
    		if (ret == 0)  //服务器端要能捕捉到客户端的退出
    		{
    			printf("client close
    ");
    			break;
    		}
    		else if (ret == -1)   
    			ERR_EXIT("read");
                    fputs(recvbuf, stdout);
                    write(conn, recvbuf, ret);
            }
    }
    
    int main(void)
    {
    	int listenfd;
    	if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    /*	if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
    		ERR_EXIT("socket");
    
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(5188);
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
    	/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/
         //这个就是上边说的那个服务器重启的解决方法
    	int on = 1;
    	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    		ERR_EXIT("setsockopt");
    
    	if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    		ERR_EXIT("bind");
    	if (listen(listenfd, SOMAXCONN) < 0)
    		ERR_EXIT("listen");
    
    	struct sockaddr_in peeraddr;
    	socklen_t peerlen = sizeof(peeraddr);
    	int conn;
    
    	pid_t pid;
    	while (1)
    	{
    		if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
    			ERR_EXIT("accept");
    
    		printf("ip=%s port=%d
    ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    
    		pid = fork();
    		if (pid == -1)
    			ERR_EXIT("fork");
    		if (pid == 0)
    		{
    			close(listenfd);//子进程只负责接收后的工作
    			do_service(conn);  //这里写了一个自定义的函数来解决子进程的操作
    			exit(EXIT_SUCCESS);
    		}
    		else
    			close(conn);  //父进程只负责监听接受连接,不负责具体的操作。
    	}
    	
    	return 0;
    }
    

    其中还有很多的细节需要注意,大家亲自试一下,首先自己动手去写,自己试各种情况有什么问题,然后再回过头来看程序和自己写的差别,才会对细节的处理理解更深刻。

    下面实现一个点对点的聊天程序:

    服务器端:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    void handler(int sig)
    {
    	printf("recv a sig=%d
    ", sig);
    	exit(EXIT_SUCCESS);
    }
    
    int main(void)
    {
    	int listenfd;
    	if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    /*	if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)*/
    		ERR_EXIT("socket");
    
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(5188);
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");*/
    	/*inet_aton("127.0.0.1", &servaddr.sin_addr);*/
    
    	int on = 1;
    	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    		ERR_EXIT("setsockopt");
    
    	if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    		ERR_EXIT("bind");
    	if (listen(listenfd, SOMAXCONN) < 0)
    		ERR_EXIT("listen");
    
    	struct sockaddr_in peeraddr;
    	socklen_t peerlen = sizeof(peeraddr);
    	int conn;
    	if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
    		ERR_EXIT("accept");
    
    	printf("ip=%s port=%d
    ", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
    
    
    	pid_t pid;
    	pid = fork();
    	if (pid == -1)
    		ERR_EXIT("fork");
    	
    	if (pid == 0)
    	{
    		signal(SIGUSR1, handler);  //这里用到了信号处理,在父进程退出的时候会发一个信号过来给子进程,然后子进程也跟着退出
    		char sendbuf[1024] = {0};
    		while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    		{
    			write(conn, sendbuf, strlen(sendbuf));
    			memset(sendbuf, 0, sizeof(sendbuf));
    		}
    		printf("child close
    ");
    		exit(EXIT_SUCCESS);
    	}
    	else
    	{
    		char recvbuf[1024];
    		while (1)
    		{
    			memset(recvbuf, 0, sizeof(recvbuf));
    			int ret = read(conn, recvbuf, sizeof(recvbuf));
    			if (ret == -1)
    				ERR_EXIT("read");
    			else if (ret == 0)
    			{
    				printf("peer close
    ");
    				break;
    			}
    			
    			fputs(recvbuf, stdout);
    		}
    		printf("parent close
    ");
    		kill(pid, SIGUSR1);//这是父进程退出前要向子进程发一个自定义的信号
    		exit(EXIT_SUCCESS);
    	}
    	
    	return 0;
    }
    

    客户端:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <signal.h>
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    void handler(int sig)
    {
    	printf("recv a sig=%d
    ", sig);
    	exit(EXIT_SUCCESS);
    }
    
    int main(void)
    {
    	int sock;
    	if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
    		ERR_EXIT("socket");
    
    	struct sockaddr_in servaddr;
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(5188);
    	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    	if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
    		ERR_EXIT("connect");
    
    	pid_t pid;
    	pid = fork();
    	if (pid == -1)
    		ERR_EXIT("fork");
    
    	if (pid == 0)
    	{
    		char recvbuf[1024];
    		while (1)
    		{
    			memset(recvbuf, 0, sizeof(recvbuf));
    			int ret = read(sock, recvbuf, sizeof(recvbuf));
    			if (ret == -1)
    				ERR_EXIT("read");
    			else if (ret == 0)
    			{
    				printf("peer close
    ");
    				break;
    			}
    			
    			fputs(recvbuf, stdout);
    		}
    		close(sock);
    		kill(getppid(), SIGUSR1);
    	}
    	else
    	{
    		signal(SIGUSR1, handler);
    		char sendbuf[1024] = {0};
    		while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    		{
    			write(sock, sendbuf, strlen(sendbuf));
    			memset(sendbuf, 0, sizeof(sendbuf));
    		}
    		close(sock);
    	}
    
    
    	
    	return 0;
    }
    

    这一节信息量比较大,得好好消化一下。

  • 相关阅读:
    poj- 2528 Mayor's posters
    POJ 2631 Roads in the North (树的直径裸题)
    Quoit Design (白话--分治--平面点对问题)
    洛古 P1020 导弹拦截 (贪心+二分)
    D
    代理模式---动态代理之Cglib
    代理模式---动态代理之JDK
    开闭原则
    迪米特法则
    接口隔离原则
  • 原文地址:https://www.cnblogs.com/DamonBlog/p/4421025.html
Copyright © 2011-2022 走看看