linux系统调用socket(TCP使用总结)

  概念:  三次握手、 四次挥手
  socket :  tcp udp 、 广播、 组播、
  并发:  
      多线程实现
      多进程实现
  第三方库来解决高并发:
      select
      poll  : 黑菜鸟驿站一样, 首先放到驿站,然后一次收

      epoll:  上面的客户端连接多了效率会地下, 这个可以解决
 
 libevent开源库: 底层封装了 epoll
 
 7天:   Json xml 格式解析

 8,9,10:  写一个小的web服务器, 模拟器http协议

1. 基础概念

 1.模型
  OSI七层模型: 物理层、数据层、网络层、传输层、会话层、表示层、应用层
  TCP/IP 4层模型:应用程、传输层(port)、网络程(ip)、链路层

  OSI: 
   应用层: 打入HTTP|FTP头
   表示层: 编码、加密
   会话层: 建立会话,TCP三次握手
   传输层: TCP/UDP/端口(FTP 21、20,HTTP 80 Telent 23), 源端口|目的端口        TCP|UDP头
   网络层: IP路由                                        源IP|目的IP            IP头
   数据链接层:Mac地址
   物理层: 网线传输

 由于osi太复杂,当时没有实现,美国国防部实现了tcp/ip,后面都用tcp/ip:
   TCP/IP:
   应用层: 协议HTTP、FTP、Telnet、ssh
   传输层:选择TCP还是UDP服务
   网络层:寻路,路由表(学习)
   物理接口层: 链路传输

   pop3: 邮局协议3,,邮件向客户端发送邮件协议
   smtp:  客户端向邮件服务器发邮件
   ICMP: ping 命令的实现
   ARP:  把Ip映射成mac地址
  RARP:   把mac地址抓转为Ip
   NAT模式
    
    TCP/IP数据打包: 
       以太网头(wifi头)|下一跳Mac地址|IP头|TCP头|HTTP协议头(应用层)|数据
    以太网数据帧
    在路由器:会解压以太网头、IP头,重新打包IP头,令牌环网

  2. udp 协议:
   16位:源端口  16位:目的端口


3. tcp 协议:
   16:源端口号
   16位:目的端口号
   32位序列号  【发送sys时候携带的序号】
   32位确定号  【 会ack的时候携带序号】
   6个标志位  【ack\syn\fin\urg\pst\pyn]
   16位窗口大小【 滑动窗口大小 】

4. Ip协议 

 网络套接字:
    一个文件描述符指向一个套接字(套接字内部借助内核的2个缓冲区实现)

 网络字节序:
计算机本地是 :小端存储
 网络:大端存储

 hton1 -> 本地-》网络IP      string(1921.68.1.11->string->aoti->int->htno1-> 网络字节序
 htons -> 本地-》网络(port)
 ntoh1 -> 网络-》本地(IP)  
 ntohs -> 网络-》本地(Port)

本地IP字节序转化为网络
 int inet_pton(int af, const char *src, void *dst);
 af: AF_INET(ipv4)、 AF_INET6(ipv6)
 src:  传入,IP地址
 dst:  传出 转化后的网络字节序的IP
 
 成功:1 
 异常:0, src不是一个有效的ip地址
 -1 : 失败
 
  网络转化本地IP字节序
  const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

 struct sockaddr: 旧的已废弃
 struct sockaddr_in       

 三次握手:
   clinet 发送 sys包(建立连接请求) 带上携带序号 500 、 数据字节数、滑动窗口大小
   server接收,同意建立连接回ack(501)确定序列号、数据字节数、滑动窗口大小,同时发送sys(700)包,我也要和你建立连接
   cient 回ack(701)
   
  四次挥手:
   【client主动关闭】client 发送 FIN(501【携带序号】), server【被动关闭】接收 回ACK应答
   那么此时client的 写缓冲区关闭,
   client 此时可以收数据,不能写数据【这种状态叫做半关闭】
   
   Server发送FIN701到client   ACK(502)        502表示502之前的信息的都收到了
   client 回ack 
   cinet 端读数据缓存区关闭,不能读数据
  
  4次挥手  断开连接
  socket中有2个缓冲区,一个用于读、一个用于写
 
 accept、connect 执行成功,标准着三次握手完成

滑动窗口:【保证数据不丢失】
   服务器返回客户端滑动窗口大小,客户端可以一直发,服务器即使没有回,服务在忙
   客户端知道服务器滑动窗口大小,
   如果客户一直发,服务器满了,后面的覆盖、或者丢失服务器不可行
   客户端知道服务器滑动窗口大小服务器满了,客户端就不会发了

2.TCP状态分析

主动发起连接 --- 被动接收连接
主动关闭连接 ---被动关闭连接

 自己理解: 

  TCP状态:  
  三次握手:
    C 【主动】 端口处于CLOSE状态, 发送sys 到 s端【被动】 s端从CLOSE到LISTEN状态
    S端回ACK和 SYS,C 端口处于SYS_SEND状态,S 端变成SYS_REVD状态
    c 回ack , s处于ESTBLISHED状态
    c 端变 ESTBLISHED状态
    C 端完成三次握手
    C 端处于ESTBLISHED状态,数据通信状态

    
  四次挥手:
   C【主动】端处于ESTBLISHED状态,发送fin,变成FIN_WAIT_1状态,
   S【被动】 端会ACK ,  C端处于FIN_WAIT_2(半关闭状态),S端从ESTBLISHED 状态变成 CLOSE_WAIT状态,
   S 端发FIN到C, C处于FIN_WAIT_2
   C 回ACK, 
   四次挥手完成
   S 此时 处于 TIME_WAIT,要过2ML时长,(40s) 进入CLOSE状态
   
   主动关闭请求一端,在关闭以后经历2ML 时长以后 CLOSE

TCP状态时序图:

    1. 主动发起连接请求端:    CLOSE -- 发送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 发送 ACK -- ESTABLISHED(数据通信态)

    2. 主动关闭连接请求端: ESTABLISHED(数据通信态) -- 发送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半关闭)

                -- 接收对端发送 FIN -- FIN_WAIT_2(半关闭)-- 回发ACK -- TIME_WAIT(只有主动关闭连接方,会经历该状态)

                -- 等 2MSL时长 -- CLOSE 

    3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)

    4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK 

                -- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE


    重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)

    netstat -apn | grep  端口号


11. 套接 字模型:  write 以后写入缓冲区,缓冲区会自动发送出去, 收到以后

fd套接字, 对应内核的缓冲区,可以读数据、写数据,

写入的时候首先写入缓冲区, 缓冲区会自动写出去,内部是一个环形队列,读完了就没有了

3. CS模型的TCP通信分析

  3.1.server

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>

#define SERVER_PORT 7777
void sys_err(const char* str){
	perror(str);
	exit(1);
}

int main(int argc,char* argv[]){

	int lfd=0,cfd=0;
	int ret,i;
	struct sockaddr_in serv_addr,clit_addr;
	socklen_t clit_addr_len;

	serv_addr.sin_family=AF_INET;   // IP V4
	serv_addr.sin_port=htons(SERVER_PORT);  // 端口, 
// htons 功能,小端转化为大端功能
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);  // INADDR_ANY 表示根据本机自动分配IP 地址

	lfd= socket(AF_INET,SOCK_STREAM,0);   // IP  流式套接子   0表示根据第2个参数推断tcp
	if(lfd==-1){
		sys_err("socket error");
	}

    // int opt = 1;		// 设置端口复用。
	// setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));

	bind(lfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr));  // 这里表示IP 地址成功绑定 socket

    listen(lfd,128);  // 这里表示设置三次握手的最大并发数, 一次,同时来128个
    clit_addr_len =sizeof(clit_addr);
    // 上面就是三次握手

// 客户端连接服务器以后 ,获取到客户端ip和port
    cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);  // 这里阻塞,在这里接收连接上的客户端
    //clit_addr 客户端数据保存在这里
// 上面文件描述符用于监听,这里用于收、发数据
    if(cfd==-1){
    	sys_err("accpet error");
    }
    char client_IP[1024]={0};
    char buf[BUFSIZ];
    printf("clinet ip:%s port:%lu\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)));

    while(1){
    	ret = read(cfd,buf,sizeof(buf));
    	write(STDOUT_FILENO,buf,ret);
    	for(i=0;i<ret;i++){
    		buf[i]= toupper(buf[i]);
    	}
    	write(cfd,buf,ret);
    }
     close(lfd);
     close(cfd);

	return 0;
}

  3.2.client

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>

#define SERVER_PORT 7777

int main(int argc,char* argv[]){

	//
	int lfd=0;
	struct sockaddr_in serv_add;
	serv_add.sin_family=AF_INET;   // 协议
    serv_add.sin_port=htons(SERVER_PORT);   //端口
    inet_pton(AF_INET,"127.0.0.1",&serv_add.sin_addr);  //本地Ip 转化为网络IP 字节序
  //  serv_add.sin_addr.s_addr=htons(INADDR_ANY);  // ip
	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd==-1){
		perror("socket errror");
		exit(-1);
	}
     int ret=connect(lfd,(struct sockaddr *)&serv_add,sizeof(serv_add)); // 客户段连接服务端
    if(ret !=0){
    	perror("connection faile");
    	exit(0);
    }
    char buf[1024]={0};
  int size=105;
    while(--size){
    	write(lfd,"hello\n",6);
    	ret =read(lfd,buf,sizeof(buf));
    	write(STDOUT_FILENO,buf,ret);
    	sleep(1);
    }
    close(lfd);


    return 0;
}

模拟客户端测试:  nc 127.0.0.1 7777 

 服务器2个socket    一个是三次握手的lfd 一个是cfd用于通信的
 客户端1个socket

例子: 首先关闭服务端,在关闭客户端,服务端处于TIME_WAIT,在启动服务器,无法启动,必须要等40s,服务端占用端口
   【未得到证实】

如何解决:

端口复用:
int opt = 1;        // 设置端口复用。
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));

关闭socket文件描述符:
    close(cfd);
    shutdown(int fd, int how);    
        how:     SHUT_RD    关读端
        SHUT_WR    关写端
        SHUT_RDWR 关读写
    shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。 

    open以后打开文件描述符f1, dup2(f1,f2) , 那么 f2 ,f1 都指向 该文件,文件描述符的引用计数+1,调用close(f1),文件描述符引用计数-1 ,只关闭了f1,f2还在, 那么如果 shutdown(f1) 那么 f1,f2都关闭

4.多进程并发服务器

出现问题解决: 

问题:
1. 共享
    * 读时共享,写时复制
    * 文件描述符
    * 内存映射区 mmap
2. 父进程的角色
   * 等待客户端连接,回收关闭socket的子进程

3. 子进程角色
  * 通信,关闭监听文件描述符 ,避免浪费资源
4. 创建进程个数限制
  * 受硬件限制
  * 文件描述符默认也有上限 1024

5.子进程资源回收
   使用信号sigaction --信号 

4.1.server

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

/***
 *  如何实现该功能: 父进程用于监听,每次连接启动一个子进程用于通信
 *  使用信号捕捉:子进程状态变化死了,使用信号监听回收子进程中父进程中
 */

#define SERVER_PORT 7777
void sys_err(const char* str){
	perror(str);
	exit(1);
}

void sig_catch4(int signo) {
	int status;
	pid_t wpid;
	// 没有子进程了回收失败
	while((wpid=waitpid(-1,&status,WNOHANG)> 0)){
	    if(WIFEXITED(status)){
	    	  printf("WIFEXITED----%d\n",WEXITSTATUS(status));
	    }
	}
	printf("---sig_catch3---%d--", signo);

}

int main(int argc, char *argv[]) {

	int lfd = 0, cfd = 0;
	int ret, i;
	pid_t pid;
	struct sockaddr_in serv_addr, clit_addr;
	socklen_t clit_addr_len;
	 char client_IP[1024]={0};

//	memset(&serv_addr,0,sizeof(serv_addr));
	bzero(&serv_addr, sizeof(serv_addr));  //和memset功能一致的

	serv_addr.sin_family = AF_INET;   // IP V4
	serv_addr.sin_port = htons(SERVER_PORT);  // 端口
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址

	lfd = socket(AF_INET, SOCK_STREAM, 0);   // IP  流式套接子   0表示根据第2个参数推断tcp
	if (lfd == -1) {
		sys_err("socket error");
	}

	bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket

	listen(lfd, 128);  // 这里表示设置三次握手的最大并发数
	clit_addr_len = sizeof(clit_addr);
	// 上面就是三次握手

	while (1) {
		memset(client_IP,0,sizeof(client_IP));
		cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
		 printf("client ip:%s port:%ld\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)));
		//clit_addr 客户端数据保存在这里
		if (cfd == -1) {
			sys_err("accpet error");
		}
		pid = fork();
		if (pid < 0) {
			sys_err("fork error");
		} else if (pid == 0) {
			close(lfd);
			break;
		} else {
			struct sigaction act, oldact;
					act.sa_handler = sig_catch4;  //设置捕捉函数
					sigemptyset(&(act.sa_mask));  // 设置mask
					act.sa_flags = 0;   //通常用0
					int ret = sigaction(SIGCHLD, &act, &oldact);  // 注册信号捕捉函数
					if (ret == -1) {
						perror("sigaction error");
						exit(-1);
					}
            close(cfd);
            continue;
		}
	}

	char buf[BUFSIZ];
	if (pid == 0) {
		while(1){
			int ret = read(cfd, buf, sizeof(buf));
				if(ret==0){  //客户端关闭,已经读到套接子的末尾
					close(cfd);
					exit(1);
				}
				for (i = 0; i < ret; i++) {
					buf[i] = toupper(buf[i]);
				}
				write(STDOUT_FILENO, buf, ret);
				write(cfd, buf, ret);
		}
	}


	return 0;
}

最终优化版本server.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
 
/***
 *  如何实现该功能: 父进程用于监听,每次连接启动一个子进程用于通信
 *  使用信号捕捉:子进程状态变化死了,使用信号监听回收子进程中父进程中
 */
 
#define SERVER_PORT 7777
void sys_err(const char* str){
	perror(str);
	exit(1);
}
 
void sig_catch4(int signo) {
	int status;
	pid_t wpid;
	// 没有子进程了回收失败
	while((wpid=waitpid(-1,&status,WNOHANG)> 0)){
	    if(WIFEXITED(status)){
	    	  printf("child died --- WIFEXITED----%d\n",WEXITSTATUS(status));
	    }
	}
//	printf("---sig_catch3---%d--", signo);
 
}
 
int main(int argc, char *argv[]) {
 
	int lfd = 0, cfd = 0;
	int ret, i;
	pid_t pid;
	struct sockaddr_in serv_addr, clit_addr;
	socklen_t clit_addr_len;
	 char client_IP[1024]={0};
 
//	memset(&serv_addr,0,sizeof(serv_addr));
	bzero(&serv_addr, sizeof(serv_addr));  //和memset功能一致的
 
	serv_addr.sin_family = AF_INET;   // IP V4
	serv_addr.sin_port = htons(SERVER_PORT);  // 端口
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
 
	lfd = socket(AF_INET, SOCK_STREAM, 0);   // IP  流式套接子   0表示根据第2个参数推断tcp
	if (lfd == -1) {
		sys_err("socket error");
	}
 
	bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
 
	listen(lfd, 128);  // 这里表示设置三次握手的最大并发数
	clit_addr_len = sizeof(clit_addr);
	// 上面就是三次握手

// 回收子进程pcb 
	struct sigaction act, oldact;
	act.sa_handler = sig_catch4;  //设置捕捉函数
	sigemptyset(&(act.sa_mask));  // 设置mask
	act.sa_flags = 0;   //通常用0
	int ret_s = sigaction(SIGCHLD, &act, &oldact);  // 注册信号捕捉函数
	if (ret_s == -1) {
			perror("sigaction error");
			exit(-1);
	}
 
	while (1) {
		memset(client_IP,0,sizeof(client_IP));
		cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
		
    // 问题?  父进程阻塞在这里, 子进程死了,父进程去处理子进程信号,回收子进程, 回收以后,accpet 不在阻塞了,返回-1
	   while(cfd == -1 &&  errno == EINTR){
	   	   cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); 
	   }
        printf("client ip:%s port:%d\n",
        	inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),
            ntohs(clit_addr.sin_port)
        	);
		//clit_addr 客户端数据保存在这里


		if (cfd == -1) {
			sys_err("accpet error");
		}
		pid = fork();
		if (pid < 0) {
			sys_err("fork error");
		} else if (pid == 0) {
			printf("i am child \n");
			close(lfd);
			break;
		} else {
			printf("i am father\n");
			
            close(cfd);
            continue;
		}
	}
 
	char buf[BUFSIZ];
	if (pid == 0) {
		while(1){
			int ret = read(cfd, buf, sizeof(buf));
				if(ret==0){  //客户端关闭,已经读到套接子的末尾
					close(cfd);
					exit(1);
				}
				for (i = 0; i < ret; i++) {
					buf[i] = toupper(buf[i]);
				}
				write(STDOUT_FILENO, buf, ret);
				write(cfd, buf, ret);
		}
	}
 
 
	return 0;
}
 

4.2.client

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>

#define SERVER_PORT 7777

int main(int argc,char* argv[]){

	//
	int lfd=0;
	struct sockaddr_in serv_add;
	serv_add.sin_family=AF_INET;   // 协议
    serv_add.sin_port=htons(SERVER_PORT);   //端口
    inet_pton(AF_INET,"127.0.0.1",&serv_add.sin_addr);  //本地Ip 转化为网络IP 字节序
  //  serv_add.sin_addr.s_addr=htons(INADDR_ANY);  // ip
	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd==-1){
		perror("socket errror");
		exit(-1);
	}
     int ret=connect(lfd,(struct sockaddr *)&serv_add,sizeof(serv_add)); // 客户段连接服务端
    if(ret !=0){
    	perror("connection faile");
    	exit(0);
    }
    char buf[1024]={0};
  int size=410;
    while(--size){
    	write(lfd,"hello\n",6);
    	ret =read(lfd,buf,sizeof(buf));
    	write(STDOUT_FILENO,buf,ret);
    	sleep(1);
    }
    close(lfd);


    return 0;
}

5.多线程服务器代码

5.1. server

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
 #include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>


#define SERVER_PORT 7777
void sys_err(const char* str){
	perror(str);
	exit(1);
}

struct s_info{
	struct  sockaddr_in cliaddr;
	int connfd;
};

void* do_work(void* arg){
	struct s_info *ts= (struct s_info*)arg;
	char buf[BUFSIZ];
	 char client_IP[1024]={0};
	while(1){
//		   *  >0 实际督导字节数
//		   *   0 读到末尾(对端已经关闭)
//		   *  -1: 读取失败  应该进一步判断errno的值
//		       errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
//		       errno = EINTR  慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
//		       errno =" 其他情况" 异常
			int n = read(ts->connfd, buf, sizeof(buf));
				if(n==0){   //客户端关闭,已经读到套接子的末尾
			printf("the client %d closed...\n",ts->connfd);
			break;
				}

				printf("receiver from client  ip:%s port:%ld\n"
						,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
						ntohs((*ts).cliaddr.sin_port));
				for (int i = 0; i < n; i++) {
					buf[i] = toupper(buf[i]);
				}
				write(STDOUT_FILENO, buf, n);
				write(ts->connfd, buf, n);
		}
	 close(ts->connfd);
	return 0;
}


int main(int argc, char *argv[]) {

	int lfd = 0, cfd = 0;
	int ret, i;
	pid_t pid;
	struct sockaddr_in serv_addr, clit_addr;
	socklen_t clit_addr_len;
	 char client_IP[1024]={0};
	 struct s_info ts[256];
	 pthread_t tid;

//	memset(&serv_addr,0,sizeof(serv_addr));
	bzero(&serv_addr, sizeof(serv_addr));  //和memset功能一致的

	serv_addr.sin_family = AF_INET;   // IP V4
	serv_addr.sin_port = htons(SERVER_PORT);  // 端口
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址

	lfd = socket(AF_INET, SOCK_STREAM, 0);   // IP  流式套接子   0表示根据第2个参数推断tcp
	if (lfd == -1) {
		sys_err("socket error");
	}

	bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket

	listen(lfd, 128);  // 这里表示设置三次握手的最大并发数
	clit_addr_len = sizeof(clit_addr);
	// 上面就是三次握手

	while (1) {
		memset(client_IP,0,sizeof(client_IP));
		cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端

		//clit_addr 客户端数据保存在这里
		if (cfd == -1) {
			sys_err("accpet error");
		}
		ts[i].cliaddr=clit_addr;
		ts[i].connfd= cfd;

		pthread_create(&tid,NULL,do_work,(void*)&ts[i]);

		pthread_detach(tid);
		i++;

       	}
	return 0;
}

优化版:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
 
 
#define SERVER_PORT 7777
void sys_err(const char* str){
  perror(str);
  exit(1);
}
 
struct s_info{
  struct  sockaddr_in cliaddr;
  int connfd ;   // 文件描述符
  pthread_t id; // 子线程id 
};
 
void* do_work(void* arg){
  struct s_info *ts= (struct s_info*)arg;
  char buf[BUFSIZ];
   char client_IP[1024]={0};
  while(1){
//       *  >0 实际督导字节数
//       *   0 读到末尾(对端已经关闭)
//       *  -1: 读取失败  应该进一步判断errno的值
//           errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
//           errno = EINTR  慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
//           errno =" 其他情况" 异常
      int n = read(ts->connfd, buf, sizeof(buf));
        if(n==0){   //客户端关闭,已经读到套接子的末尾
              printf("the client %d closed...\n",ts->connfd);
              break;
        }else if(n == -1){
          perror("error");
          pthread_exit(NULL);  // 退出自己
        }else{
        printf("receiver from client  ip:%s port:%d  tid:%ld  fd: %d \n"
            ,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
            ntohs((*ts).cliaddr.sin_port),
            ts->id,
            ts->connfd
            );
        for (int i = 0; i < n; i++) {
          buf[i] = toupper(buf[i]);
        }
        write(STDOUT_FILENO, buf, n);
        write(ts->connfd, buf, n);
      }
    }
   close(ts->connfd);
  return 0;
}
 
 
int main(int argc, char *argv[]) {
 
  int lfd = 0, cfd = 0;
  int ret, i;
  pid_t pid;
  struct sockaddr_in serv_addr, clit_addr;
  socklen_t clit_addr_len;
   char client_IP[1024]={0};
   struct s_info ts[256];  //为什么要定义一个数组
   pthread_t tid;
 
//  memset(&serv_addr,0,sizeof(serv_addr));
  bzero(&serv_addr, sizeof(serv_addr));  //和memset功能一致的
 
  serv_addr.sin_family = AF_INET;   // IP V4
  serv_addr.sin_port = htons(SERVER_PORT);  // 端口
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
 
  lfd = socket(AF_INET, SOCK_STREAM, 0);   // IP  流式套接子   0表示根据第2个参数推断tcp
  if (lfd == -1) {
    sys_err("socket error");
  }
 
  bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
 
  listen(lfd, 128);  // 这里表示设置三次握手的最大并发数
  clit_addr_len = sizeof(clit_addr);
  // 上面就是三次握手
 
  while (1) {

    memset(client_IP,0,sizeof(client_IP));
    cfd = accept(lfd, (struct sockaddr*) &clit_addr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
 
    //clit_addr 客户端数据保存在这里
    if (cfd == -1) {
      sys_err("accpet error");
    }
    ts[i].cliaddr=clit_addr;
    ts[i].connfd= cfd;
 
  //  pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
        pthread_create(&ts[i].id,NULL,do_work,(void*)&ts[i]);
    // ts[i].id = tid; 
 
    // pthread_detach(tid);
    pthread_detach(ts[i].id);
    i++;
      //数组满了 , 不收了
    if(i==256){
        break;  
    }
    }
    close(lfd);
    // 数组满了,退出主线程, 子线程继续跑 
    pthread_exit(NULL);
 
  return 0;
}

上面的只能开256个线程, 如果回收了保证复用

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
 
 
#define SERVER_PORT 7777
void sys_err(const char* str){
  perror(str);
  exit(1);
}
 
struct s_info{
  struct  sockaddr_in cliaddr;
  int connfd ;   // 文件描述符
  pthread_t id; // 子线程id 
};
 
void* do_work(void* arg){
  struct s_info *ts= (struct s_info*)arg;
  char buf[BUFSIZ];
   char client_IP[1024]={0};
  while(1){
//       *  >0 实际督导字节数
//       *   0 读到末尾(对端已经关闭)
//       *  -1: 读取失败  应该进一步判断errno的值
//           errno = EAGAIN or EWOULDBLOCK: 设置了非阻塞方式读
//           errno = EINTR  慢速系统调用被 中断 , 解决方法重新 重新运行读取 goto
//           errno =" 其他情况" 异常
      int n = read(ts->connfd, buf, sizeof(buf));
        if(n==0){   //客户端关闭,已经读到套接子的末尾
              printf("the client %d closed...\n",ts->connfd);
              break;
        }else if(n == -1){
          perror("error");
          pthread_exit(NULL);  // 退出自己
        }else{
        printf("receiver from client  ip:%s port:%d  tid:%ld  fd: %d\n"
            ,inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,client_IP,sizeof(client_IP)),
            ntohs((*ts).cliaddr.sin_port),
            ts->id,
            ts->connfd);
        for (int i = 0; i < n; i++) {
          buf[i] = toupper(buf[i]);
        }
        write(STDOUT_FILENO, buf, n);
        write(ts->connfd, buf, n);
      }
    }
   close(ts->connfd);
  return 0;
}
 
 
int main(int argc, char *argv[]) {
 
  int lfd = 0, cfd = 0;
  int ret, i;
  pid_t pid;
  struct sockaddr_in serv_addr, clit_addr;
  socklen_t clit_addr_len;
   char client_IP[1024]={0};
   struct s_info ts[256];  //为什么要定义一个数组
   pthread_t tid;
 
//  memset(&serv_addr,0,sizeof(serv_addr));
  bzero(&serv_addr, sizeof(serv_addr));  //和memset功能一致的
 
  serv_addr.sin_family = AF_INET;   // IP V4
  serv_addr.sin_port = htons(SERVER_PORT);  // 端口
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示根据本机自动分配IP 地址
 
  lfd = socket(AF_INET, SOCK_STREAM, 0);   // IP  流式套接子   0表示根据第2个参数推断tcp
  if (lfd == -1) {
    sys_err("socket error");
  }
 
  bind(lfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); // 这里表示IP 地址成功绑定 socket
 
  listen(lfd, 128);  // 这里表示设置三次握手的最大并发数
  clit_addr_len = sizeof(clit_addr);
  // 上面就是三次握手

  // if have thread is exit,  use 
  for(i=0; i<  256 ;i++){
    ts[i].connfd = -1; 
  }

 
  while (1) {
  for(i=0; i< 256 ;i++){
       if(ts[i].connfd == -1){
         break; 
       }
  }
    //数组满了 , 不收了
    if(i==256){
        break;  
    }
    
    memset(client_IP,0,sizeof(client_IP));
    ts[i].connfd = accept(lfd, (struct sockaddr*) &ts[i].cliaddr, &clit_addr_len); // 这里阻塞,在这里接收连接上的客户端
    printf("liangshang...\n");
    //clit_addr 客户端数据保存在这里
    if (ts[i].connfd  == -1) {
      sys_err("accpet error");
    }
  //  ts[i].cliaddr=clit_addr;
 
  //  pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
        pthread_create(&ts[i].id,NULL,do_work,(void*)&ts[i]);
    // ts[i].id = tid; 
 
    // pthread_detach(tid);
    pthread_detach(ts[i].id);
  //  i++;
    }
    close(lfd);
    // 数组满了,退出主线程, 子线程继续跑 
    printf("main exit\n");
    pthread_exit(NULL);
 
  return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值