tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手

socket编程

  • 网络字节序
    • 定义
      • 为什么网络数据需要转换为网络字节序?
  • Udp协议
  • Tcp协议
  • Udp的socket编程
    • 编程流程
    • 接口
      • 创建套接字
      • 绑定地址信息
      • 发送
      • 接收
      • 关闭
      • 主机字节序转换成为网络字节序
      • 将ip地址从点分十进制转化为无符号32位整数并转换为网络字节序
      • 将网络字节序转换为主机字节序并转换为点分十进制的ip地址
    • 代码实现
      • 绑定地址
      • udp程序
        • 服务端
      • 客户端
      • 运行udp程序
  • Tcp的socket编程
    • Tcp的编程流程(三次握手四次挥手)
    • Tcp socket编程的接口
      • 创建套接字
      • 绑定地址信息
      • 监听
      • 发起连接
      • 建立新连接
      • 发送数据
      • 接收数据
      • 关闭套接字
    • Tcp代码实现
      • 判断端口是否在监听
      • 单线程实现Tcp代码
        • 服务端
        • 客户端
      • 多进程实现Tcp代码
        • 服务端
      • 多线程实现Tcp代码

网络字节序

定义

什么是字节序:CPU对内存数据的存取顺序

大端字节序&小端字节序
	大端	:低位保存在高地址	小端:低位保存在低地址
		78					12
		56					34
		34					56
		12					78

网络字节序:采用大端字节序传输,防止出现大小端机器解读数据出错的情况

主机字节序:主机字节序指的是机器具体的字节序
	主机是大端:认为主机字节序是大端
	主机是小端:认为主机字节序是小端

网络设计需要继续转发之前都要将主机字节序转换成网络字节序

网络数据接收之前都需要将网络字节序转换成主机字节序

为什么网络数据需要转换为网络字节序?

1、网络规定采用大端字节序作为网络字节序
2、路由设备或者交换机需要对网络数据进行分用到网络层,以获得“目的ip地址”,而这些设备在继续分用的时候默认是按照网络字节序进行分用的

Udp协议

无连接:当udp的客户端想要给udp服务端发送数据的时候,只需要知道服务端的ip和端口,就可以直接发送
	引申含义:在发送数据之前是不知道服务端的状态信息的
	
不可靠:不报证udp的数据一定到达对端机器

面向数据报:udp数据是整条数据接收和发送的

Tcp协议

有连接:连接双方在发送数据之前,需要进行连接(指需要提前告知,沟通连接的信息)

可靠传输:保证数据是有序且可靠到达对端

面向字节流:上一次和下一次的数据是没有明显的数据边界的

Udp的socket编程

编程流程

cs模型(一般使用):client(客户端)-server(服务端)
bs模型:浏览器-服务器

客户端												服务端
1、创建套接字											1、创建套接字
2、绑定地址信息(不推荐自己绑定)						2、绑定地址信息:ip地址、端口信息
3、先由客户端向服务端发送数据				
													3、再由服务端向客户端回复数据
4、关闭套接字											4、关闭套接字

接口

创建套接字

int socket(int domain, int type, int protocol);
domain:指定当前的地址域,相当于指定网络层到底使用什么协议
	AF_INET:ipv4网络(默认选择)
	AF_INET6:ipv6网络
	AF_UNIX:本地域套接字

type:创建套接字的类型
	SOCK_DGRAM:用户数据报套接字 =》udp协议
	SOCK_STREAM:流式套接字 =》tcp协议

protocol:
	0:表示使用套接字类型默认的协议
	IPPROTO_UDP:17
	IPPROTO_TCP:6

返回值:
	返回桃姐字描述符,套接字描述符本质上就是一个文件描述符
	成功:大于等于0
	失败:小于0

创建套接字的原因在于想在内核当中创建一块与协议相对应的缓冲区
需要包含一个头文件
#include 
如果protocol一栏中不使用默认的0则需要包含一个头文件
#include  
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 
  6 int main()
  7 {
  8   int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  9   if(sockfd < 0)
 10   {
 11     perror("socket fail\n");
 12     return 0;
 13   }
 14 
 15   printf("sockfd : %d\n", sockfd);
 16   while(1)
 17   {
 18     sleep(1);
 19   }
 20   return 0;
 21 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第1张图片
tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第2张图片

会发现在闪烁,此时闪烁的原因是它指向了内核中的缓冲区

绑定地址信息

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket函数返回的套接字描述符
	将创建出来的套接字和网卡、端口进行绑定
addr:地址信息结构
	struct sockaddr---》通用地址信息结构
		struct sockaddr {
          				 sa_family_t sa_family;		//地址域信息,占用两个字节
          											//地址域信息决定了当前使用寿命网络层协议
        			     char        sa_data[14];   //本质上任何网络程序都不会直接填充这个14个字节的字符组,
        			     						    //定义第二个参数总共占用14个字节
   					    }
   					    
						   ———————————————
				        |	2字节地址域信息  	|
				        |		一一		  	|
				        |		 ↑			|
				        |	14字节地址数据	|
				        |		 ↓			|
				        |		一一			|
						   ———————————————
						   
	ipv4:
		vim /usr/include/netinet/in.h
		struct sockaddr_in
  		{
  			__SOCKADDR_COMMON (sin_);											//地址域信息:2字节
 			in_port_t sin_port;                 /* Port number.  */				//端口信息:2字节
			struct in_addr sin_addr;            /* Internet address.  */		//ipv4版本的ip地址:4字节
			/* Pad to size of `struct sockaddr'.  */							//填充剩下的部分为0:8字节
			unsigned char sin_zero[sizeof (struct sockaddr) -
                 __SOCKADDR_COMMON_SIZE -
                 sizeof (in_port_t) -
                 sizeof (struct in_addr)];
	    };
	    
	   					   ———————————————
	    				|	2字节地址域信息	|
	    				|	 2字节端口信息	|
	    				|		4字节		|
	    				|	    ip地址		|
	    				|		一一			|
	    				|		 ↑			|
	    				|	  8字节填充为0	|
	    				|		 ↓			|
	    				|		一一			|
	    				    ———————————————
	    				
	地址域信息:网络协议栈通过该信息,来判断传递进来的是哪一个数据结构,判断出来之后,就知道后面的字节该如何解析
	
	本地域套接字:进行本地进程间通信的一种手段
		struct sockaddr_un

						   ———————————————
						|	2字节地址域信息	|
						|		一一			|
						|		 ↑			|
						|	108字节路径名		|
						|		 ↓			|
						|		一一			|
						   ———————————————
						   
addrlen:地址信息结构的长度,防止越界

发送

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
spckfd:套接字描述符
buf:想要发送的数据
len:要发送数据的长度
flags:
	0:阻塞发送
dest_addr:目标主机的地址信息结构(ip,port(端口))
addrlen:目标主机地址信息结构的长度
返回值:
	成功:返回具体发送的字节数量
	失败:返回-1

客户端为什么不推荐绑定地址信息?
	本质上是不想让那个客户端程序将端口写死
	即不想让客户端在启动的时候都是绑定一个端口(一个端口只能被一个协议当中的一个进程所绑定)
	客户端1绑定了端口,本机在启动客户端2的时候会绑定失败,因为端口已经被客户端1拿走了
	客户端没有主动的绑定端口,udp客户端在调用sendto的时候,会自动绑定(操作系统分配)一个空闲的端口

接收

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:套接字描述符
buf:将数据接收到buf当中
len:最大可以接收多大的数据
flags:
	0:阻塞接收
src_addr:接收的数据来源的主机地址信息结构
addrlen:输入输出型参数
	输入:在接收之前准备的对端地址信息结构的长度
	输出:实际接收回来的地址信息长度
返回值:
	接收到:接收的字节数量
	未接收:-1
eg:
char* buf[1024] = {0};		<--数据接收的位置
struct sockaddr_in src_addr;   <--定义一个具体结构体例如ipv4
recvfrom(sockfd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&src_addr, )
						↑预留\0				↑传递结构体的地址,需要强转为(struct sockaddr*)
接收完后src_addr中有ip和port,即发送数据的主机的ip地址和端口

关闭

close(int sockfd);

主机字节序转换成为网络字节序

接口1:h(ost)ton(et)l(long) --> htonl 4字节      
      uint32_t htonl(uint32_t hostlong);      
      如果主机是大端机调用接口则什么都不干,如果主机是小端机则会将数据转换成小端再返回
      
接口2:htons(hort) --> htons 2字节         
      uint16_t htons(uint16_t hostshort);

网络字节序转换成为主机字节序
接口1:ntohl 4字节          
	  uint32_t ntohl(uint32_t netlong);
	  
接口2:ntohs 2字节  
	  uint16_t ntohs(uint16_t netshort);

将ip地址从点分十进制转化为无符号32位整数并转换为网络字节序

in_addr_t inet_addr(const char *cp);

将网络字节序转换为主机字节序并转换为点分十进制的ip地址

char *inet_ntoa(struct in_addr in);

代码实现

绑定地址

首先测试一下绑定地址信息

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/socket.h> //包含了各种数据结构
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 
  7 int main()
  8 {
  9   //创建套接字
 10   int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 11   if(sockfd < 0)
 12   {
 13     perror("socket failed\n");
 14     return 0;
 15   }
 16   //绑定
 17   struct sockaddr_in addr;//定义一个ipv4的端口
 18   addr.sin_family = AF_INET;//地址域,告诉操作系统内核传递的是哪种结构体
 19   addr.sin_port = htons(12345);//传递需要监听的端口,需要转换成为网络字节序
 20   addr.sin_addr.s_addr = inet_addr("0.0.0.0");	//ip地址保存的变量
 21   //0.0.0.0表示当前机器任一一块网卡的ip地址                                 
 22   //127.0.0.1表示当前机器本地回环网卡地址
 23   
 24   int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
 25   if(ret < 0)
 26   {
 27     perror("bind failed\n");
 28     return 0;
 29   }
 30 
 31   while(1)
 32   {
 33     sleep(1);
 34   }
 35   return 0;
 36 }

在这里插入图片描述
此时没有任何输出,说明while(1)之前的步骤都成功了
tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第3张图片

可以看到确实是创建成功了,可以使用 netstat -anp | grep [端口] 来查看端口使用情况

在这里插入图片描述

udp 表示当前12345端口使用的是udp协议
0.0.0.0:12345 代表当前监听的ip地址与端口
0.0.0.0:* 表示接收从任一ip和任一端口来的数据

如果此时再启动一次程序,会报错,因为端口已经被占用了

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第4张图片

udp程序

服务端
  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/socket.h> //包含了各种数据结构
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 
  8 int main()
  9 {
 10   //创建套接字
 11   int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 12   if(sockfd < 0)
 13   {
 14     perror("socket failed\n");
 15     return 0;
 16   }
 17   //绑定
 18   struct sockaddr_in addr;//定义一个ipv4的端口
 19   addr.sin_family = AF_INET;//地址域,告诉操作系统内核传递的是哪种结构体
 20   addr.sin_port = htons(12345);//传递需要监听的端口,需要转换成为网络字节序
 21   addr.sin_addr.s_addr = inet_addr("10.0.8.14");//ip地址保存的变量
 22                                               //0.0.0.0表示当前机器任一一块网卡的ip地址
 23                                               //127.0.0.1表示当前机器本地回环网卡地址
 24 
 25   int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
 26   if(ret < 0)
 27   {
 28     perror("bind failed\n");
 29     return 0;
 30   }
 31 
 32   while(1)
 33   {
 34     //接收部分
 35 
 36     char buf[1024] = {0};
 37     struct sockaddr_in peer_addr;
 38     socklen_t peer_addrlen = sizeof(peer_addr);
 39     ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer_addr, &peer_addrlen);
 40     if(recv_size < 0)
 41     {
 42       continue;//接收失败继续接收
 43     }
 44     //成功
 45     printf("i am server, i recv %s, from %s:%d\n", buf, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
 46 
 47     //发送部分
 48 
 49     memset(buf, '\0', sizeof(buf));//清空buf
 50     sprintf(buf, "hello, client %s:%d, i am server\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));
 51     //写入到buf中
 52     //发送
 53     sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&peer_addr, sizeof(peer_addr));
 54   }
 55   return 0;
 56 }

客户端

  1 #include <stdio.h> 
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/socket.h> //包含了各种数据结构
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 
  8 int main()
  9 {
 10   //创建套接字
 11   int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 12   if(sockfd < 0)
 13   {
 14     perror("socket failed\n");
 15     return 0;
 16   }
 17 
 18   //不用绑定
 19 
 20   while(1)
 21   {
 22     //发送数据
 23     char buf[1024] = "i am client1";
 24 
 25     struct sockaddr_in dest_addr;
 26     dest_addr.sin_family = AF_INET;
 27     dest_addr.sin_port = htons(12345);//需要发送到的端口
 28     dest_addr.sin_addr.s_addr = inet_addr("10.0.8.14");
 29     int se = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
 30     if(se < 0)
 31     {
 32       continue;
 33     }
 34 
 35     //接收数据
 36     memset(buf, '\0', sizeof(buf));
 37     ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);//发送时已经获得了服务端的信息
 38     if(recv_size < 0)
 39     {
 40       perror("recvfrom failed\n");
 41       continue;
 42     }
 43 
 44     printf("server say: %s\n", buf);
 45   }
 46   return 0;
 47 }

运行udp程序

运行一下两个程序
tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第5张图片
tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第6张图片

Tcp的socket编程

Tcp的编程流程(三次握手四次挥手)

客户端															服务端
1、创建套接字														1、创建套接字
	流式套接字														创建流式套接字(SOCK_STREAM)
2、不推荐绑定地址信息												2、绑定地址信息
																	ip/port
3、发起连接														3、监听
																	①告诉操作系统内核,可以接收客户端发起的连接请求
																	②监听新连接的到来
																		三次握手建立连接
																	4、接收新连接
								。。。
								互相发送数据
								。。。
							    四次挥手断开连接
							   
完成三次握手后即建立连接完成
	三次握手的过程是在内核(网络协议栈)中完成的,建立连接的过程不需要程序员参与
	第一次:客户端给服务端发送连接请求
	第二次:服务端回复客户端的连接请求并发送连接请求
	第三次:客户端回复服务端的连接请求,连接建立
断开连接需要四次挥手

Tcp socket编程的接口

创建套接字

int socket(int domain, int type, int protocol);

绑定地址信息

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

监听

int listen(int sockfd, int backlog);
sockfd:套接字描述符
backlog:已完成连接队列的大小

	三次握手中:
		客户端向服务端发送一个名为SYN的数据包,表示发起连接
		服务端收到请求后向客户端发送SYN+ACK两个数据包,其中SYN代表服务端回复也想建立连接,ACK是回复上一个SYN的
		客户端向服务端回复一个ACK数据包,用于回复服务端发送的SYN数据包
	
	此时内核中有两个队列,一个叫未完成连接队列,一个叫已完成连接队列
		已完成连接队列:
			1、已经完成三次握手等待被accept的连接
			2、连接状态为“连接建立”的连接
		为完成连接队列:
			没有完成三次握手的连接

	backlog影响了服务端并发接收连接的能力
		假设backlog是1,此时未完成队列中的数量为2,即此时只能有一个能进入已完成连接队列,在进入已完成连接队列后该新
		建立的连接被拿走,又有一个连接可以进入已完成连接队列,即吞吐量为1

发起连接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:套接字描述符
addr:服务端的地址信息结构(服务端的ip和服务端的端口)
addrlen:服务端地址信息结构长度

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

建立新连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:从已完成连接队列当中获取已经完成三次握手的连接

sockfd:套接字描述符
addr:客户端的地址信息结构(客户端的ip和客户端的端口)
addrlen:客户端地址信息结构的长度

返回值:
	成功:新连接的套接字描述符,也是个文件描述符
	失败:-1
	
	①TCP服务端在通信之前创建套接字A,套接字当中有发送缓冲区和接收缓冲区,在绑定地址信息和监听后便可以接收新连接
	
	②此时应该客户端发起连接,发起连接后与TCP服务端创建的套接字A产生了关联,即三次握手的数据是在套接字A中处理的
	
	③调用accept后会在服务端中再创建一个套接字B,也就是新连接的套接字,随后客户端与套接字B进行数据的收发
	
	④套接字A为监听套接字即“listen sock”,套接字B为“new sock”

	⑤若此时再有一个客户端想建立连接,发起连接后同样是与套接字A进行三次握手建立连接,在建立连接后客户端会再创建一个
	新套接字C与新的客户端进行数据收发

如果没有连接的时候,调用accept也会阻塞

发送数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:套接字描述符
	accept的返回值即新连接的套接字
buf:待要发送的数据
len:发送数据的长度
flags:
	0:阻塞发送
	MSG_OBB:发送带外数据
		
返回值:
	大于0:返回发送的字节数量
	-1:发送失败

接收数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:套接字描述符
	accept的返回值即新连接的套接字
buf:将接收的数据放到哪里去
len:buf的最大接收能力
flags:0 阻塞接收

返回值:
	大于0:正常接收了多少字节数据
	等于0:对端将连接关闭了
	小于0:接收失败

如果连接没有数据,则调用recv会阻塞

关闭套接字

int close(int fd);

Tcp代码实现

判断端口是否在监听

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 
  7 int main()
  8 {
  9   //建立套接字
 10   int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 11   if(listen_sock < 0)
 12   {
 13     perror("listen_sock failed\n");
 14     return 0;
 15   }
 16 
 17   //绑定
 18   struct sockaddr_in addr;
 19   addr.sin_family = AF_INET;
 20   addr.sin_port = htons(12345);
 21   addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
 22   int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
 23   if(ret < 0)
 24   {
 25     perror("bind failed\n");
 26     return 0;
 27   }
 28 
 29   //监听
 30   ret = listen(listen_sock, 1);
 31   if(ret < 0)
 32   {
 33     perror("listen failed\n");
 34     return 0;
 35   }
 36 
 37   while(1)
 38   {
 39     sleep(1);
 40   }
 41   return 0;
 42 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第7张图片
可以看到端口12345现在正在处于LISTEN状态

单线程实现Tcp代码

服务端
   1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 
  8 int main()
  9 {
 10   //创建套接字
 11   int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 12   if(listen_sock < 0)
 13   {
 14     perror("listen_sock failed\n");
 15     return 0;
 16   }
 17 
 18   //绑定
 19   struct sockaddr_in addr;
 20   addr.sin_family = AF_INET;
 21   addr.sin_port = htons(12345);
 22   addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
 23   int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
 24   if(ret < 0)
 25   {
 26     perror("bind failed\n");
 27     return 0;
 28   }
 29 
 30   //监听
 31   ret = listen(listen_sock, 1);
 32   if(ret < 0)
 33   {
 34     perror("listen failed\n");
 35     return 0;
 36   }
 37 
 38   while(1)
 39   {
 40     //建立新连接
 41     struct sockaddr_in peer_addr;
 42     socklen_t peer_addrlen = sizeof(peer_addr);
 43     int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
 44     if(new_sock < 0)
 45     {
 46       perror("accept failed\n");
 47       return 0;
 48     }
 49 
 50     //接收成功
 51     printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
 52 
 53 
 54     //先接收
 55     char buf[1024] = {0};
 56     ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
 57     if(recv_size < 0)
 58     {
 59       perror("recv failed\n");
 60       continue;
 61     }
 62     else if(recv_size == 0)//对端关闭
 63     {
 64       perror("peer shutdown\n");
 65       close(new_sock);//对端关闭则关闭自己
 66 
 67       close(listen_sock);
 68       return 0;
 69     }
 70 
 71     //接收成功
 72     printf("client %d say: %s\n", new_sock, buf);
 73 
 74     //发送
 75     memset(buf, '\0', sizeof(buf));//清空buf
 76     sprintf(buf, "i am server, hello client %d\n", new_sock);
 77 
 78     ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
 79     if(send_size < 0)
 80     {
 81       perror("send failed\n");
 82       continue;
 83     }
 84   }
 85 
 86   close(listen_sock);
 87   return 0;
 88 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第8张图片

客户端
  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <unistd.h>
  4 #include <sys/socket.h>
  5 #include <netinet/in.h>
  6 #include <arpa/inet.h>
  7 
  8 int main()
  9 {
 10   int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 11   if(sockfd < 0)
 12   {
 13     perror("sockfd failed\n");
 14     return 0;
 15   }
 16 
 17   //连接
 18   struct sockaddr_in dest_addr;
 19   dest_addr.sin_family = AF_INET;
 20   dest_addr.sin_port = htons(12345);
 21   dest_addr.sin_addr.s_addr = inet_addr("159.75.12.23");
 22   int ret = connect(sockfd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
 23   if(ret < 0)
 24   {
 25     perror("connect failed\n");
 26     return 0;
 27   }
 28 
 29   while(1)
 30   {
 31     //发送
 32     char buf[1024] = "i am tcp client 1";
 33     ssize_t send_size = send(sockfd, buf, strlen(buf), 0);
 34     if(send_size < 0)
 35     {
 36       perror("send failed\n");
 37       continue;
 38     }
 39 
 40     //接收
 41     memset(buf, '\0', sizeof(buf));
 42     ssize_t recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
 43     if(recv_size < 0)
 44     {
 45       perror("recv fail\n");
 46       continue;
 47     }
 48     else if(recv_size == 0)
 49     {
 50       printf("server shutdown\n");
 51       close(sockfd);
 52       return 0; 
 53     }
 54 
 55     printf("server say: %s\n", buf);
 56   }
 57 
 58   return 0;
 59 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第9张图片

多进程实现Tcp代码

客户端同单线程

服务端
    1 #include <stdio.h>
    2 #include <string.h>
    3 #include <unistd.h>
    4 #include <sys/socket.h>
    5 #include <netinet/in.h>
    6 #include <arpa/inet.h>
    7 #include <signal.h>
    8 #include <sys/wait.h>
    9 
W> 10 void sigcallback(int signo)
   11 {
   12   wait(NULL);
   13 }
   14 
   15 int main()
   16 {
   17   signal(SIGCHLD, sigcallback);
   18 
   19   //创建套接字
   20   int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   21   if(listen_sock < 0)
   22   {
   23     perror("listen_sock failed\n");
   24     return 0;
   25   }
   26 
   27   //绑定
   28   struct sockaddr_in addr;
   29   addr.sin_family = AF_INET;
   30   addr.sin_port = htons(12345);
   31   addr.sin_addr.s_addr = inet_addr("10.0.8.14");//使用ifconfig邦定内网地址
   32   int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
   33   if(ret < 0)
   34   {
   35     perror("bind failed\n");
   36     return 0;
   37   }
   38 
   39   //监听
   40   ret = listen(listen_sock, 1);
   41   if(ret < 0)
   42   {
   43     perror("listen failed\n");
   44     return 0;
   45   }
   46 
   47   while(1)
   48   {
   49     //建立新连接
   50     struct sockaddr_in peer_addr;
   51     socklen_t peer_addrlen = sizeof(peer_addr);
   52     int new_sock = accept(listen_sock, (struct sockaddr*)&peer_addr, &peer_addrlen);
   53     if(new_sock < 0)
   54     {
   55       perror("accept failed\n");
   56       return 0;
   57     }
   58 
   59     //接收成功
   60     printf("accept %s:%d, create new_sock %d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), new_sock);
   61 
   62     pid_t pid = fork();//创建子进程
   63     if(pid < 0)
   64     {
   65       perror("fork failed\n");
   66       close(new_sock);
   67       continue;
   68     }
   69     else if(pid == 0)
   70     {
   71       //child
   72       //recv
   73       close(listen_sock);//防止子进程受父进程中套接字的影响 
   74       while(1)
   75       {
   76         //先接收
   77         char buf[1024] = {0};
   78         ssize_t recv_size = recv(new_sock, buf, sizeof(buf) - 1, 0);
   79         if(recv_size < 0)
   80         {
   81           perror("recv failed\n");
   82           continue;
   83         }
   84         else if(recv_size == 0)//对端关闭
   85         {
   86           perror("peer shutdown\n");
   87           close(new_sock);//对端关闭则关闭自己
   88 
   89           close(listen_sock);
   90           return 0;
   91         }
   92 
   93         //接收成功
   94         printf("client %d say: %s\n", new_sock, buf);
   95 
   96         //发送
   97         memset(buf, '\0', sizeof(buf));//清空buf
   98         sprintf(buf, "i am server, hello client %d\n", new_sock);
   99 
  100         ssize_t send_size = send(new_sock, buf, strlen(buf), 0);
  101         if(send_size < 0)
  102         {
  103         perror("send failed\n");
  104         continue;
  105         }
  106       }
  107     }
  108     else
  109     {
  110       //father
  111       close(new_sock);
  112       continue;
  113     }
  114   }
  115 
  116   close(listen_sock);
  117   return 0;
  118 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第10张图片

多线程实现Tcp代码

#pragma once//防止头文件重复包含 
  2 
  3 #include <stdio.h>
  4 #include <string.h>
  5 #include <unistd.h>
  6 #include <pthread.h>
  7 #include <sys/socket.h>
  8 #include <netinet/in.h>
  9 #include <arpa/inet.h>
 10 #include <string>
 11 
 12 using namespace std;
 13 
 14 class Tcp_Svr
 15 {
 16   public:
 17     Tcp_Svr()
 18     {
 19       sockfd_ = -1;
 20     }
 21 
 22     ~Tcp_Svr()
 23     {
 24       //close(sockfd_);
 25       //可在此close或新建一个close接口
 26     }
 27 
 28     int CreatSocket()//创建套接字
 29     {
 30       //sockfd
 31       sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 32       if(sockfd_ < 0)
 33       {
 34         perror("sockfd_ failed");
 35         return -1;
 36       }
 37       return sockfd_;
 38     }
 39 
 40     int Bind(const string& ip = "0.0.0.0", uint16_t port = 12345)//绑定地址信息
 41     {
 42       //使用sockfd
 43       struct sockaddr_in addr;
 44       addr.sin_family = AF_INET;
 45       addr.sin_port = htons(port);
 46       addr.sin_addr.s_addr = inet_addr(ip.c_str());
 47       int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
 48       if(ret < 0)
 49       {
 50         perror("bind failed");
 51         return -1;
 52       }
 53       return 0;
 54     }
 55 
 56     int Listen(int backlog = 5)//监听
 57     {
 58       //使用sockfd
 59       int ret = listen(sockfd_, backlog);
 60       if(ret < 0)
 61       {
 62         perror("listen failed");
 63         return -1;
 64       }
 65       return 0;
 66     }
 67 
 68     int Accept(struct sockaddr_in* peer_addr)//建立连接
 69     {
 70       //使用sockfd
 71       socklen_t peer_addrlen = sizeof(struct sockaddr_in);
 72       //如果直接给peer_addr到sizeof中只会传递一个指针的大小,传递一个类型的大小才是正确的
 73       int ret = accept(sockfd_, (struct sockaddr*)peer_addr, &peer_addrlen);
 74       if(ret < 0)
 75       {
 76         peer_addr = NULL;
 77         return -1;
 78       }
 79 
 80       return ret;
 81     }
 82 
 83     void Close()
 84     {
 85       close(sockfd_);
 86     }
 87   
 88     void SetSockFd(int sockfd)
 89     {
 90       sockfd_ = sockfd;
 91     }
 92     
 93     int Send(const string& data)//发送
 94     {
 95       ssize_t send_size = send(sockfd_, data.c_str(), data.size(), 0);
 96       if(send_size < 0)
 97       {
 98         perror("send failed");
 99         return -1;
100       }
101       return send_size;
102     }
103 
104     int Recv(string* data)
105     {
106       data->clear();//防止有脏数据
107       char buf[1024] = {0};
108       ssize_t recv_size = recv(sockfd_, buf, sizeof(buf) - 1, 0);
109       if(recv_size < 0)
110       {
111         perror("recv failed");
112         return -1;
113       }
114       else if(recv_size == 0)
115       {
116         printf("peer shutdown\n");
117         return -100;
118       }
119 
120       data->assign(buf, strlen(buf));
121       return recv_size;
122     }
123 
124   private:
125     int sockfd_;
126 };
  1 #include "tcp_thread.hpp"  
  2 
  3 #define CHECK_RET(v) if(v < 0){return -1;}
  4 
  5 void* tcp_thread_start(void* arg)
  6 {
  7   pthread_detach(pthread_self());
  8   Tcp_Svr* new_ts = (Tcp_Svr*)arg;
  9 
 10   while(1)
 11   {
 12     //接收
 13     string data = "";
 14     int ret = new_ts->Recv(&data);
 15     if(ret == -100)
 16     {
 17       new_ts->Close();
 18       delete new_ts;//释放
 19       pthread_exit(NULL);
 20     }
 21     else if(ret <= -1)
 22     {
 23       continue;
 24     }
 25     else if(ret > 0)//大于0代表接收到了内容
 26     {
 27       printf("client say:%s\n", data.c_str());
 28     }
 29     
 30     //发送
 31     data.clear();
 32 
 33     data.assign("i am server");
 34     new_ts->Send(data);
 35   }
 36 
 37   return NULL;
 38 }
 39 
 40 int main()
 41 {
 42   Tcp_Svr ts;
 43   CHECK_RET(ts.CreatSocket());
 44   CHECK_RET(ts.Bind());
 45   CHECK_RET(ts.Listen());
 46 
 47   while(1)
 48   {
 49     struct sockaddr_in peer_addr;
 50     int new_sockfd = ts.Accept(&peer_addr);
 51     if(new_sockfd < 0)
 52     {
 53       continue;//接收失败继续接收
 54     }
 55 
 56     //代码执行到这里,说明接收了一个新的客户端
 57     //1、创建线程(在线程内部让创建出来的线程自己把自己分离)
 58     //2、给线程传递的参数怎么传递
 59     
 60     Tcp_Svr* new_ts = new Tcp_Svr();
 61     if(new_ts == NULL)
 62     {
 63       //关闭套接字
 64       close(new_sockfd);
 65       continue;
 66     }
 67 
 68     new_ts->SetSockFd(new_sockfd);
 69 
 70     pthread_t tid;
 71     int ret = pthread_create(&tid, NULL, tcp_thread_start, (void*)new_ts);
 72     if(ret < 0)
 73     {
 74       perror("pthread_create failed");
 75       //关闭新连接套接字,以触发客户端感知到服务端有误了
 76       new_ts->Close();
 77       delete new_ts;
 78       continue;
 79     }
 80 
 81     //主线程再次while(1)循环,进行accept
 82   }
 83   return 0;
 84 }

tcp与udp的socket编程,udp代码实现客户端服务端,tcp代码实现客户端服务端(单线程、多线程、多进程分别实现),三次握手_第11张图片

你可能感兴趣的:(linux,网络,socket)