网络编程知识三之TCP协议与常见服务器模型

一.tcp(传输控制协议)协议简介

协议: 双方约定好的数据包格式,类似于快递公司让我们要在固定位置填收货人地址和电话号码。
特点:
1>面向连接
1)三次握手
2)四次挥手

2>保证数据包的可靠性
数据无丢失,数据包无错误, 数据包无失序,数据无重复达到

TCP协议使用的产品:
发送邮件,数据下载,QQ登录 …

3>TCP创建流程图
在这里插入图片描述

注意: 1. 监听套接字(套接字被设置成监听模式,专门接受客户端连接请求)
2. 每一步流程的作用

4>TCP数据传输原理
在这里插入图片描述

注意: 1. 客户端的socket和服务端新建立的socket之间建立连接,建立连接后,好像在服务端和客户端之间建立了两个管道, 分别负责从客户端传输数据到服务端和从服务端传输数据到客户端。
2. 服务端和每个客户端都建立了独立连接。
3. 服务端套接字和客户端套接字都有发送缓冲和接收缓冲。正是因为有缓冲的存在,才会有read write对缓冲区的读写。

二.TCP通信模型设计

服务器端:(被动接受请求)
 
socket            //电话机 
  |              
bind(ip+port)     //绑定电话号码 。绑定服务器自己的ip和port,等待客户端连接。
  |
listen            //监听有人打电话进来
  |
accept            //接听电话
  |
recv/send         //通话过程
  |
close             //挂机
客户端:(主动发起连接)
socket            //电话机 
  |              
bind(ip+port)     //绑定电话号码
  |
connect          //拨打电话 
  |
recv/send         //通话过程
  |
close             //挂机

三.TCP常用函数的实现。
1>创建流式套接字

 int socket(int domain, int type, int protocol);
       功能: 创建socket ,返回对应的文件描述符
       参数:
            @domain    域(通信的范围)
            @type     SOCK_STREAM  流式套接字: 有序,可靠,面向连接 字节流
                      SOCK_DGRAM   报文套接字:无连接的,不可靠的
                      SOCK_RAW     原始套接字: 可以访问一些低层的网络协议

            @protocol    0表示默认的方式
                        SOCK_STREAM    TCP 
                        SOCK_DGRAM     UDP
    返回值:
             成功  文件描述符
             失败  -1 ,并设置errno

2>把服务器的ip和port和sockfd绑定

 int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
   功能:
      绑定一个地址(ip+port)到一个socket 文件描述符上

    参数:
       @sockfd   socket 函数获得的文件描述符
       @addr     地址信息结构体
                 
               //通用结构体
               struct sockaddr {
                   sa_family_t sa_family; //地址族
                   char	   sa_data[14];   //地址信息
               }
               //TCP/IP协议的地址结构
               struct sockaddr_in {
                    sa_family_t    sin_family;  //协议簇
                    in_port_t      sin_port;    //端口
                    struct in_addr sin_addr;   //ip地址
                };
  
        @addrlen     表示 addr 参数对应类型的地址信息结构体的大小

    返回值:
         成功 0
         失败 -1&errno

操作:
1>.定义地址结构体变量,清零

  struct sockaddr_in  ser_addr;
  memset(&ser_addr,0,sizeof(ser_addr));

2>.填充地址信息

 ser_addr.sin_family  = AF_INET;//地址协议族
 ser_addr.sin_port    = htons(8888);//端口号
 ser_addr.sin_addr    = inet_addr("192.168.2.7");

3>. 绑定

 if(bind(sockfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr)) < 0)
  {
      perror("bind fail");
      exit(EXIT_FAILURE);
  }

3>获得客户连接请求,创建连接连接套接字。负责数据通信。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  功能: 获取连接请求,建立连接套接字

        参数:
            @sockfd   监听套接字
            @addr     用来获取对端的地址信息
            @addrlen  值结果参数(注意:必须自己初始化一个值sizeof(addr))
        返回值:
            成功  新连接的套接字文件描述符
            失败  -1 ,并设置错误码。

特点:
1.请求队列中没有请求,阻塞调用者
2.每次提取一路请求,就会创建一个新的套接字,我们称为连接套接字,用来和客户端收发数据

int listen(int sockfd ,int backlog);
功能:把同一个时刻来的套接字存放如监听队列中。<同一个时刻>
参数:
@sockfd         监听的套接字
@backlog        连接请求队列的最大长度。一般设置为30 以下。

注意:有的时候backlog很小,但是我们也可以了解很多台机器。服务器机器处理速度很快队列来不及填满就处理完了,而且在同一个时刻到来的连接还是很少的

4>客户端向服务器请求连接的函数

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:向服务端发起连接。
参数:
            @sockfd  客户端的socket文件描述符
            @addr    代表服务器端的地址信息
            @addrlen   addr 对应的地址结构体类型的大小
返回值:
成功返回0,失败返回-1 

5>发送数据

 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:给指定的sockfd发送数据
参数:
        @scokfd     套接字
        @ buf         发送数据的缓冲区
        @len           要发送数据的长度
        @flags       设置网络标志   ,常常设置为0
返回值:
成功,返回值 > 0,发送成功的字节数
      返回值==0,发送超时
      返回值<0, 发送失败,并设置错误码

//在阻塞模式下,下面的公式等价
注:send(sockfd, buf, len, 0); <===> write(sockfd, buf, len);

6>接收数据

 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收指定的sockfd的数据即连接套接字返回的文件描述符
参数:
      @scokfd     套接字
        @ buf         接受数据的缓冲区
        @len           要接受数据的长度
        @flags      设置网络标志   ,常常设置为0
返回值:
成功,返回值 > 0,成功接收的字节数
      返回值==0,对方关闭了连接
      返回值<0, 发送失败,并设置错误码    

//在阻塞模式下,下面的公式等价
注:recv(sockfd, buf, len, 0); <===> read(sockfd, buf, len);

练习:
1.TCP服务端和客户端的代码看懂。然后客户端发送数据,服务端接收数据,然后服务端打印接收到的内容,在原样的发送给客户端,之后,客户端再来接收数据,之后把客户端再把数据打印到屏幕上。依次如此。

客户端 服务端
send---------->recv
recv <--------send

Service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");
		

	//5.准备接收客户端的连接请求
	connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);		
	if(connect_fd < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	printf("=============================================");
	printf("connect_fd : %d\n",connect_fd);
	printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
	printf("client port : %d\n", ntohs(client_addr.sin_port));

	while(1)
	{
		memset(buf,0,sizeof(buf));
		n = recv(connect_fd,buf,sizeof(buf),0);	
		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

		printf("Recv %d bytes : %s\n",n,buf);
	}

	close(listen_fd);
	close(connect_fd);
	exit(EXIT_SUCCESS);
}

Client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int sockfd = 0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(server_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&server_addr,0,sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.准备向服务端的请求连接
	if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		memset(buf,0,sizeof(buf));
		putchar('>');
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';

		n = send(sockfd,buf,strlen(buf),0);	

		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

	}

	close(sockfd);
	exit(EXIT_SUCCESS);
}


上面的主要是只能实现一对一的通信。服务器开启之后,只能有一路连接。

四.常见服务器模型
1>TCP循环服务器
TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接.
算法如下:

socket(...);     //创建一个TCP套接字
bind(...);     //绑定公认的端口号
listen(...);     //监听客户端连接
while(1)     //开始循环接收客户端连接
{ 
        accept(...);         //接收当前客户端的连接
        while(1) 
        {     //处理当前客户端的请求
                read(...); 
                process(...); 
                write(...); 
        } 
        close(...);         //关闭当前客户端的连接,准备接收下一个客户端连接
} 

注意:
TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的.

service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	//创建socket并返回对应的文件描述符listen_fd
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");
		

	while(1)
	{
		//5.接收客户端的连接请求,创建连接套接字,返回新建立的
		//套接字文件描述符connect_fd
		connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);		
		if(connect_fd < 0)
		{
			perror("Fail to accept");	
			exit(EXIT_FAILURE);
		}

		printf("=============================================\n");
		printf("connect_fd : %d\n",connect_fd);
		printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
		printf("client port : %d\n", ntohs(client_addr.sin_port));

		while(1)
		{
			memset(buf,0,sizeof(buf));
			//6.接受数据
			n = recv(connect_fd,buf,sizeof(buf),0);	
			//返回值>0,成功接收的字节数
			//返回值==0,对方关闭了连接
			//返回值<0,发送失败,并设置错误码
			if(n < 0)
			{
				perror("Fail to recv!\n");	
				exit(EXIT_FAILURE);
			}else if(n == 0){
				printf("clinet is not connect\n");	
				exit(EXIT_FAILURE);
			}

			if(strncmp(buf,"quit",4) == 0)
				break;

			printf("Recv %d bytes : %s\n",n,buf);
		}
	}
	close(listen_fd);
	close(connect_fd);
	exit(EXIT_SUCCESS);
}


client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int sockfd = 0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(server_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器的ip地址和端口
	memset(&server_addr,0,sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.准备向服务端的请求连接
	if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		memset(buf,0,sizeof(buf));
		putchar('>');
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';

		n = send(sockfd,buf,strlen(buf),0);	

		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

	}

	close(sockfd);
	exit(EXIT_SUCCESS);
}


通过不同的客户机给服务器发送数据,但每次服务器只会处理一个客户端。

并发服务器:
为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是
每一个客户机的请求并不由服务器直接处理,而是服务器创建一个"子进程"[线程]来处理. 算法如下:

1.  多进程TCP并发服务器 
  socket(...); 
  bind(...); 
  listen(...); 
  while(1) 
  {
    connect_fd = accept(...); 
    pid = fork();
       if(pid == 0) { 
        close(listen_fd) ; //创建子进程,套接字复制一份,关闭不需要使用。 
        while(1) 
        {         
           read(...); 
           process(...); 
           write(...); 
         } 
             } 
		close(connect_fd); //父进程关闭连接套接字,只需要监听即可。 

     close(...); 
  }       

service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define N 1024

void do_client(int sockfd)
{
	char buf[N] = {0};
	int n = 0;

	while(1)
	{
		memset(buf,0,sizeof(buf));
		n = recv(sockfd,buf,sizeof(buf),0);	
		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

		printf("Recv %d bytes : %s\n",n,buf);
	}
	exit(EXIT_SUCCESS);//子进程退出后会造成僵尸态子进程
}

void handler_process(int signum)
{
	waitpid(-1,NULL,WNOHANG);
	printf("chlid exit is success!\n");
	return;
}

//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;
	pid_t pid; //unsigned int 
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//对僵尸态子进程进行回收
	//子进程结束时会给父进程发一个SIG信号,此时调用
	//回收僵尸态子进程处理函数
	if(signal(SIGCHLD,handler_process) == SIG_ERR){
	  perror("Fail to signal");
	  exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");


	while(1)
	{
		//5.准备接收客户端的连接请求
		connect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);		
		if(connect_fd < 0)
 		{
			perror("Fail to accept");	
			exit(EXIT_FAILURE);
		}

		printf("=============================================\n");
		printf("connect_fd : %d\n",connect_fd);
		printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
		printf("client port : %d\n", ntohs(client_addr.sin_port));

		pid = fork();

		if(pid < 0)
		{
			perror("Fail to fork");
			exit(EXIT_FAILURE);
		}else if(pid == 0){   //child process do something  
			close(listen_fd); //防止子进程浪费文件描述符	
			do_client(connect_fd);		
		}else if(pid > 0){
			close(connect_fd); //父进程关闭连接套接字	,只需要进行监听即可
		}	
	}

	exit(EXIT_SUCCESS);
}

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int sockfd = 0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(server_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&server_addr,0,sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.准备向服务端的请求连接
	if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		memset(buf,0,sizeof(buf));
		putchar('>');
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';

		n = send(sockfd,buf,strlen(buf),0);	
		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

	}

	close(sockfd);
	exit(EXIT_SUCCESS);
}


2.  多线程TCP并发服务器 。
//线程处理函数
void *do_client(void *age)
{
        int connect_fd = *(int *)arg;
        while(1)
        {
                //接收客户端数据
                recv();..
        }
}

  socket(...); 
  bind(...); 
  listen(...); 
  while(1) 
  {
    pconnect_fd = malloc(sizeof(int)); //在堆区创建空间保持文件描述符

     *pconnect = accept(...); 
       //创建线程接收数据  
       ret = pthread_creat(&tind,NULL,do_client,pconnect_fd);
         close(...); 
         exit(...); 
     } 
       pthread_detach(tid);
}    

思考:这里为什么需要在堆区创建空间呢?
答案:它主要是在堆区创建空间用于保存每个客户端的连接套接字。若是局部变量的话,每来了一个用户就会把之间的值给覆盖掉。若是全局变量,也是一样,每来了一个用户就会把之间的值给覆盖掉。若是数组,也可以,但是由于不知道连接用户的个数,数组比较浪费空间。

service.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>

#define N 1024

void *do_client(void *arg)
{
	int n = 0;
	char buf[N] = {0};
	int sockfd = *(int *)arg;
	//下面pthread_create传递过来的参数
 //即连接套接字的地址,sockfd就是连接套接字了
	while(1)
	{
		memset(buf,0,sizeof(buf));
		n = recv(sockfd,buf,sizeof(buf),0);	
		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

		printf("Recv %d bytes : %s\n",n,buf);
	}

	close(sockfd);
	free(arg);//释放堆区空间
	pthread_exit(NULL);
}

//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int listen_fd = 0;
	int connect_fd = 0;
	struct sockaddr_in my_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(my_addr);
	int n = 0 ;
	int *pconnect_fd = NULL;
	pthread_t tid;
	int ret = 0;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器的ip地址和端口
	memset(&my_addr,0,sizeof(my_addr));	
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(atoi(argv[2]));
	my_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.把ip和port进行绑定
	if(bind(listen_fd,(struct sockaddr *)&my_addr,len) < 0)
	{
		perror("Fail to bind");	
		exit(EXIT_FAILURE);
	}

	//4.监听客户端的连接
	listen(listen_fd,5);
	printf("Listen....\n");


	while(1)
	{
		//由于每次有用户连接的时候,系统都会调用accept来返回连接套接字。
		//若是同时两个2个用户请求连接,可能存在,后一个线程把前一个线程
		//的数据给覆盖点,因此在堆区申请空间,每一个连接套接字拥有不同的
		//地址空间.
		pconnect_fd = (int *)malloc(sizeof(int));

		//5.准备接收客户端的连接请求
		*pconnect_fd = accept(listen_fd,(struct sockaddr *)&client_addr,&len);		
		if(*pconnect_fd < 0)
		{
			perror("Fail to accept");	
			exit(EXIT_FAILURE);
		}

		printf("=============================================");
		printf("connect_fd : %d\n",*pconnect_fd);
		printf("client IP : %s\n",inet_ntoa(client_addr.sin_addr));
		printf("client port : %d\n", ntohs(client_addr.sin_port));

		ret = pthread_create(&tid,NULL,do_client,(void *)pconnect_fd);
		//pconnect_fd是给线程函数do_client传递的参数
		if(ret != 0)
		{
			fprintf(stderr,"Fail to pthread_create : %s\n",strerror(errno));	
			exit(EXIT_FAILURE);
		}
		
		//分离式线程:线程结束的时候由系统自动来释放资源
		pthread_detach(tid);
	}

	exit(EXIT_SUCCESS);
}


#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	       /* See NOTES */
#include <stdlib.h>
#include <string.h>

#define N 1024
//./a.out ip port
int main(int argc, const char *argv[])
{
	char buf[1024] = {0};
	int sockfd = 0;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int len = sizeof(server_addr);
	int n = 0 ;
	if(argc < 3)
	{
		fprintf(stderr,"Usage : %s ip port!\n",argv[0]);	
		exit(EXIT_FAILURE);
	}

	//1.通过socket创建监听套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("Fail to socket");	
		exit(EXIT_FAILURE);
	}

	//2.填充服务器自己的ip地址和端口
	memset(&server_addr,0,sizeof(server_addr));	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(atoi(argv[2]));
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//3.准备向服务端的请求连接
	if(connect(sockfd,(struct sockaddr *)&server_addr,len) < 0)
	{
		perror("Fail to accept");	
		exit(EXIT_FAILURE);
	}

	while(1)
	{
		memset(buf,0,sizeof(buf));
		putchar('>');
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';

		n = send(sockfd,buf,strlen(buf),0);	

		if(n < 0)
		{
			perror("Fail to recv!\n");	
			exit(EXIT_FAILURE);
		}else if(n == 0){
			printf("clinet is not connect\n");	
			exit(EXIT_FAILURE);
		}

		if(strncmp(buf,"quit",4) == 0)
			break;

	}

	close(sockfd);
	exit(EXIT_SUCCESS);
}


注:TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程/子线程是一种非常消耗资源的操作.

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值