Linux_socket(TCP篇)网络编程

一、TCP客户端实现

tcp 协议是⾯向连接的协议,在实现 tcp 客户端时,则需要先连接服务器,后⾯才能进⾏通讯。

image.png
在整个数据传输流程中,主要涉及以下几个接口:

  • socket( ) : 创建套接字, 使⽤的套接字类型为流式套接字
  • connect( ) : 连接服务器
  • send( ) : 数据发送
  • recv( ) : 数据接收

1.socket( )

函数头⽂件:
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:int socket(int domain,int type,int protocol)

函数功能:创建套接字

函数参数:
	domain: 协议族,如 AF_INTE (表示IPV4)
    type : 套接字类型
		SOCK_STREAM : 流式套接字, 传输层使⽤ tcp 协议
		SOCK_DGRAM : 数据包套接字, 传输层使⽤ udp 协议
	protocol : 协议, 可以填0

函数返回值 
	成功 : 返回 套接字⽂件描述符
	失败 : 返回 -1,并设置 errno

2.connect( )

函数头⽂件:
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

函数功能:发起对套接字的连接 (基于连接的协议进行)

函数参数:
    sockfd : 套接字⽂件描述符
	addr : 连接的套接字的地址结构对象的地址 (⼀般为服务器)
    internet 协议族使⽤的 struct sockaddr_in 结构体,⼤⼩与通⽤ struct sockaddr 结构体⼀致
    addrlen : 地址结构的⻓度

函数返回值:
	成功 : 返回 0
	失败 : 返回 -1,并设置 errno

示例: 编码实现客户端主程序,并连接服务器。

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

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

    //2.填写协议与服务器网络信息
	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //3.连接服务器
	int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("[ERROR] Fail to connect.");
		exit(EXIT_FAILURE);
	}

	close(sfd);

	
	return 0;
}

3.send( )

基于 socket 发送数据需要调⽤ send 函数, 下⾯是 send 函数的具体信息。

函数头⽂件:
	#include <sys/types.h>
	#include <sys/socket.h>

函数原型:ssize_t send(int sockfd,const void *buf,size_t len,int flags)

函数功能:基于套接字(建⽴连接后)发送数据

函数参数:
	sockfd : 套接字⽂件描述符
	buf : 发送缓冲区的地址
	len : 发送数据的⻓度
	flags : 发送标志位

函数返回值:
	成功 : 返回 成功发送的字节数
	失败 : 返回 -1, 并设置 errno

示例:客户端发送数据给服务器

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

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("[ERROR] Fail to connect.");
		exit(EXIT_FAILURE);
	}
	
	//2.获取输入并发送
	ssize_t sbytes = 0;
	char buffer[1024] = {0};
	strcpy(buffer, "Hello, server");
	sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
	if(sbytes == -1){
		perror("[ERROR] Fail to send.");
		exit(EXIT_FAILURE);
	}

	close(sfd);

	return 0;
}

4.recv( )

基于 socket 接收数据需要调⽤ recv 函数, 具体信息如下。

函数头⽂件: 
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:ssize_t recv(int sockfd,void *buf,size_t len,int flags)

函数功能:基于套接字接收数据

函数参数: 
	sockfd : 套接字⽂件描述符
	buf : 接收缓冲区的地址
	len : 接收数据最⼤⻓度
	flags : 标志位

函数返回值:
	成功 : 返回 成功接收的字节数
	失败 : 返回 -1, 并设置 errno

示例:服务器发送数据,客户端接收数据(实现客户端的接收功能)

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

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("[ERROR] Fail to connect.");
		exit(EXIT_FAILURE);
	}
	
	//2.获取输入并发送
	ssize_t sbytes = 0;
	char buffer[1024] = {0};
	strcpy(buffer, "Hello, server");
	sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
	if(sbytes == -1){
		perror("[ERROR] Fail to send.");
		exit(EXIT_FAILURE);
	}

	bzero(buffer, sizeof(buffer));

	//3.接收数据并显示
	ssize_t rbytes = 0;
	char buffer_recv[1024] = {0};
	rbytes = recv(sfd, buffer_recv, sizeof(buffer_recv), 0);
	if(ret == -1){
		perror("[ERROR] Fail to ercv.");
		exit(EXIT_FAILURE);
	}else if(rbytes > 0){
		printf("buffer : %s\n", buffer_recv);
	}else if(rbytes == 0){
		printf("server has been shut down.\n");
	}

	close(sfd);
    
	return 0;
}

二、TCP服务端实现

将服务端的环节拆分,可得到以下流程图:

image.png
在上述流程中,相对于客户端,服务端主要增加以下新的操作:

  • bind : 绑定 ip 地址与端⼝号,⽤于客户端连接服务器
  • listen : 建⽴监听队列,并设置套接字的状态为 listen 状态, 表示可以接收连接请求
  • accept : 接受连接, 建⽴三次握⼿, 并创建新的⽂件描述符, ⽤于数据传输

在整个过程中,socket 套接字状态如下图:
image.png

  • CLOSED : 关闭状态。
  • LISTEN:套接字的状态为 listen 状态, 表示可以接收连接请求。
  • SYN-SENT : 套接字正在试图主动建⽴连接 [发送 SYN 后还没有收到 ACK],很短暂。
  • SYN-RECEIVE : 正在处于连接的初始同步状态 [收到对⽅的 SYN,但还没收到⾃⼰发过去的 SYN 的 ACK]。
  • ESTABLISHED : 连接已建⽴。

1.bind( )

函数头⽂件:
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

函数功能:绑定 ip 地址与端⼝号

函数参数:
	sockfd : 套接字⽂件描述符
	buf : 接收缓冲区的地址
	len : 接收数据最⼤⻓度
	flags : 标志位

函数返回值:
	成功 : 返回 成功接收的字节数
	失败 : 返回 -1, 并设置 errno

示例: 设计⼀个基本的服务器程序, 完成 socket 创建并绑定 ip 地址与端⼝号

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

#define BACKLOG 10

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//2.绑定ip地址与端口号
	int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
	if(ret == -1){
		perror("[ERROR] Failed to bind.");
		exit(EXIT_FAILURE);
	}

	//3.设置套接字状态为监听状态,并建立监听队列
	ret = listen(sfd, BACKLOG);
	if(ret == -1){
		perror("[ERROR] Failed to listen.");
		exit(EXIT_FAILURE);
	}

	//4.同意建立连接——accept
	struct sockaddr_in cli_addr; //用来保存客户端的地址信息
	socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
	int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
	if(cfd == -1){
		perror("[ERROR] Failed to accept.");
		exit(EXIT_FAILURE);
	}

	printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

	//while(1){}

	close(sfd);

	return 0;
}

2.listen( )

在服务器绑定 ip 地址与端⼝号之后, 则需要让服务器 socket 套接字设置成被动监听状态,并 创建监听队列,这⾥需要调⽤ listen 函数。

函数头⽂件:
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:int listen(int sockfd,int backlog)

函数功能:设置套接字状态为被动监听,并创建监听队列

函数参数:
	sockfd : 套接字⽂件描述符
	backlog : 监听队列的⻓度

函数返回值:
	成功 : 返回 0
	失败 : 返回 -1, 并设置 errno

3.accept( )

函数头⽂件:
    #include <sys/types.h>
	#include <sys/socket.h>

函数原型:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)

函数功能:接受来⾃于其他 socket 的连接请求,并建⽴连接

函数参数:
	sockfd : 套接字⽂件描述符
	addr : ⽹络地址结构的指针(输出参数,⽤于保存发送请求端的地址信息)
	addrlen : ⽹络地址结构⻓度的指针 (输出参数,但是需要进⾏初始化)

函数返回值:
    成功 : 返回新的⽂件描述符
	失败 : -1 , 并设置 errno

示例:设计⼀个服务器程序,并和客户端建⽴连接,并打印客户端的 ip 地址和端⼝号

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

#define BACKLOG 10

int main(int argc, const char *argv[])
{
	ssize_t rbytes, sbytes = 0; //接收send和recv返回值
	char buffer[1024] = {0}; //数据缓冲区

	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//2.绑定ip地址与端口号
	int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
	if(ret == -1){
		perror("[ERROR] Failed to bind.");
		exit(EXIT_FAILURE);
	}

	//3.设置套接字状态为监听状态,并建立监听队列
	ret = listen(sfd, BACKLOG);
	if(ret == -1){
		perror("[ERROR] Failed to listen.");
		exit(EXIT_FAILURE);
	}

	//4.同意建立连接——accept
	struct sockaddr_in cli_addr; //用来保存客户端的地址信息
    bzero(&cli_addr, sizeof(struct sockaddr)); //清空
    
	socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
	int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
	
    if(cfd == -1){
		perror("[ERROR] Failed to accept.");
		exit(EXIT_FAILURE);
	}

	printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

    close(cfd);
	close(sfd);

	return 0;
}

添加功能:服务端打印出客户端的网络信息之后,进入死循环持续接收来自客户端发送来的消息,并将接收的消息以相同的内容发送回应给客户端。
image.png

//服务端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

#define BACKLOG 10

int main(int argc, const char *argv[])
{
	ssize_t rbytes, sbytes = 0; //接收send和recv返回值
	char buffer[1024] = {0}; //数据缓冲区

	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//2.绑定ip地址与端口号
	int ret = bind(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr));
	if(ret == -1){
		perror("[ERROR] Failed to bind.");
		exit(EXIT_FAILURE);
	}

	//3.设置套接字状态为监听状态,并建立监听队列
	ret = listen(sfd, BACKLOG);
	if(ret == -1){
		perror("[ERROR] Failed to listen.");
		exit(EXIT_FAILURE);
	}

	//4.同意建立连接——accept
	struct sockaddr_in cli_addr; //用来保存客户端的地址信息
	socklen_t len = sizeof(struct sockaddr_in); //用来保存地址结构体的长度
	int cfd = accept(sfd, (struct sockaddr *)&cli_addr, &len);
	if(cfd == -1){
		perror("[ERROR] Failed to accept.");
		exit(EXIT_FAILURE);
	}

	printf("ip : %s, port : %d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

	while(1)
	{
		rbytes = recv(cfd, buffer, sizeof(buffer), 0);
		if(rbytes == -1){
			perror("[ERROR] Failed to recv.");
			exit(EXIT_FAILURE);
		}
		else if(rbytes > 0)
		{
			sbytes = send(cfd, buffer, sizeof(buffer), 0);
			if(rbytes == -1)
			{
				perror("[ERROR] Failed to recv.");
				exit(EXIT_FAILURE);
			}
		}
		else if(rbytes == 0)
		{
			printf("the client has been shut down.\n");
			exit(EXIT_SUCCESS);
		}
	}

    close(cfd);
	close(sfd);
	
	return 0;
}
//客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		fprintf(stderr, "Usage : %s <ip> <prot>.\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1){
		perror("[ERROR] Fail to socket.");
		exit(EXIT_FAILURE);
	}

	struct sockaddr_in svr_addr;
	bzero(&svr_addr, sizeof(struct sockaddr_in)); //清零
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);

	int ret = connect(sfd, (const struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
	if(ret == -1){
		perror("[ERROR] Fail to connect.");
		exit(EXIT_FAILURE);
	}
	
	//2.获取输入并发送
	ssize_t sbytes = 0;
	char buffer[1024] = {0};
	strcpy(buffer, "Hello"); //这里以 Hello 作为输入
    
	sbytes = send(sfd, buffer, strlen(buffer)+1, 0);
	if(sbytes == -1){
		perror("[ERROR] Fail to send.");
		exit(EXIT_FAILURE);
	}

	bzero(buffer, sizeof(buffer));

	//3.接收数据并显示
	ssize_t rbytes = 0;
	char buffer_recv[1024] = {0};
    
	rbytes = recv(sfd, buffer_recv, sizeof(buffer_recv), 0);
	if(ret == -1)
    {
		perror("[ERROR] Fail to ercv.");
		exit(EXIT_FAILURE);
	}
    else if(rbytes > 0)
    {
		printf("buffer : %s\n", buffer_recv);
	}
    else if(rbytes == 0)
    {
		printf("server has been shut down.\n");
	}

	close(sfd);

	return 0;
}

三、粘包问题

TCP是面向字节流的协议,流就像河流中的水,TCP对数据包是以“组”的方式进行发送,而并非是一次发送全部的数据包,这么做是为了提升传输效率。在这个过程中,TCP默认使用Nagle算法,而Nagle算法主要做两件事:
1)只有上一个分组得到确认,才会发送下一个分组;
2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象,也就是说数据发送出来它已经是粘包的状态了。
由于分组的存在,包与包之间没有明确的界限,所以会在发送时产生粘包现象。当网络传输数据的速度大于接收方处理数据的速度时,这时候就会导致,接收方在读取缓冲区时,缓冲区存在多个数据包。而且在 TCP 协议中,接收方是一次读取缓冲区中的所有内容,所以不能反映原本的数据信息。
而UDP是基于数据报的协议,它有消息边界,所以不会出现粘包现象。

TCP粘包的解决方案主要有两种:

  • 方法⼀ : 使⽤定⻓数据包,每次必须要读取固定⻓度的数据,适⽤于数据⻓度是固定的场景。
    image.png
  • 方法⼆ : 使⽤数据⻓度 + 数据的⽅式,先接收一个4字节存储的数据⻓度,再根据数据⻓度值接收数据,这⾥就结合第⼀种⽅式,进⾏固定⻓度接收,这种⽅式适⽤于不定⻓数据场景。
    image.png

第二种方法是最常用的。
以第二种方法为思路,编写客户端与服务端程序。

//客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc,char *argv[])
{
	int sfd,ret;
	struct sockaddr_in svr_addr;
	ssize_t sbytes = 0;
	char buffer[] = "Hello,server";
	char *pbuffer = NULL;
	int length = 0;
	if (argc != 3)
    {
		fprintf(stderr,"Usage : %s < ip > < port >.\n",argv[0])	;
		exit(EXIT_FAILURE);
	}

	sfd = socket(AF_INET,SOCK_STREAM,0);
	if (sfd == -1)
    {
		perror("[ERROR] Failed to socket.");
		exit(EXIT_FAILURE);
	}

	bzero(&svr_addr,sizeof(struct sockaddr_in));
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	ret = connect(sfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));
	if (ret == -1){
		perror("[ERROR] Failed to connect.");
		exit(EXIT_FAILURE);
	}

	for(;;){
        
		length = strlen(buffer);
		
        pbuffer = (char *)malloc(length + 4);
		memcpy(pbuffer, &length,4);        //给前4个字节写入数据长度
		memcpy(pbuffer + 4,buffer,length); //写入数据
		
        sbytes = send(sfd,pbuffer,length + 4,0);
		
        if (sbytes == -1){
			perror("[ERROR] Failed to send.");
			exit(EXIT_FAILURE);
			
		}
	}

	close(sfd);

	return 0;
}
//服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>	
#include <sys/socket.h>

#define BACKLOG 10

int main(int argc,char *argv[])
{
	int sfd,ret,cfd;
	struct sockaddr_in svr_addr,cli_addr;
	ssize_t rbytes = 0,sbytes = 0;
	char buffer[1024] = {0};
	int length;
	int total_received;
	socklen_t len = sizeof(struct sockaddr_in);
	if (argc != 3){
		fprintf(stderr,"Usage : %s < ip > < port >.\n",argv[0])	;
		exit(EXIT_FAILURE);
	}

	//1.创建套接字
	sfd = socket(AF_INET,SOCK_STREAM,0);
	if (sfd == -1){
		perror("[ERROR] Failed to socket.");
		exit(EXIT_FAILURE);
	}

	bzero(&svr_addr,sizeof(struct sockaddr_in));
	svr_addr.sin_family = AF_INET;
	svr_addr.sin_port = htons(atoi(argv[2]));
	svr_addr.sin_addr.s_addr = inet_addr(argv[1]);
	
	//2.绑定ip地址与端口号
	ret = bind(sfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));
	if (ret == -1){
		perror("[ERROR] Failed to bind.");
		exit(EXIT_FAILURE);
	}

	ret = listen(sfd,BACKLOG);
	if (ret == -1){
		perror("[ERROR] Failed to listen.");
		exit(EXIT_FAILURE);
	}

	cfd = accept(sfd,(struct sockaddr *)&cli_addr,&len);
	if (cfd == -1){
		perror("[ERROR] Failed to accept.");
		exit(EXIT_FAILURE);

	}
	printf("ip : %s port : %d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));

	for(;;)
    {
		length =  0;  //存储数据长度
		total_received = 0; //存储总长 
		rbytes = recv(cfd,&length,4,0); //读4个字节放到length里,length的值现在是数据长度
		if (rbytes == -1)
        {
			perror("[ERROR] Failed to recv.");
			exit(EXIT_FAILURE);
		}

		for(;;)
        {
			rbytes = recv(cfd, buffer + total_received, length - total_received, 0);
			if (rbytes == -1)
            {
				perror("[ERROR] Failed to recv.");
				exit(EXIT_FAILURE);

			}
            else if (rbytes > 0)
            {
				printf("buffer : %s\n",buffer);
				total_received += rbytes;
				if (total_received == length)
					break;
			}
            else if (rbytes == 0)
            {
				printf("The client has been shutdown.\n");
				break;
			}
		}
		printf("buffer : %s\n",buffer);
		sleep(1);
	}

	close(sfd);
	return 0;
}
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值