TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端

TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端

  • 1.回顾服务器端与客户端函数的调用关系
  • 2.实现hello_word服务器端与客户端
    • (1)服务器端
    • (2)修改的服务器端
    • (3)客户端
    • (4)修改的客户端
    • (5)运行
  • 3.实现回声服务器端与客户端
    • (1)服务器端实现
    • (2)客户端实现
    • (3)运行
    • (4)修改客户端

在前3篇文章中,我们大致了解了网络编程与套接字、服务端与客户端各函数的使用方法,本篇将在此基础上完成一个简单的hello_word通信,并修改为迭代服务端与客户端

1.回顾服务器端与客户端函数的调用关系

下图说明了TCP服务端与客户端的实现顺序,对于整个流程还有疑问的、或者不太理解的可以翻阅理解网络编程与套接字,我们把整个过程比作了打电话,便于大家理解。其中 close(),read(),write()函数我们在第一节基于Linux的文件操作有简单的讲解与实现,而剩下的socket(),bind(),listen(),accept(),connect()函数在网络编程各基础函数的使用方法有详细的说明。本节TCP服务端与客户端的实现本质是把各函数揉在了一起。
TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第1张图片

2.实现hello_word服务器端与客户端

(1)服务器端

TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第2张图片
我们只需要把六个调用过程实现就行,如果还有疑问请在上篇查阅各函数的参数类型使用方法。

#include 
#include 
#include 
#include 
#include 
#include 
  
int main(int argc, char *argv[])
{
     
    char message[] = "Hello world!";//要发送的信息
    
  	//创建套接字socket()
  	int serv_sock;
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    
  	//分配ip地址与端口号
  	struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));
    bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr);
    
  	//进入等待连接请求状态listen()
    listen(serv_sock, 5) ;
    
  	//受理客户端请求accept()
  	int clnt_sock;
  	struct sockaddr_in clnt_addr;
  	socklen_t clnt_addr_size;
    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
    
  	//write与close
    write(clnt_sock, message, sizeof(message));
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

(2)修改的服务器端

上述服务器端可以完成但是网络编程的函数往往有返回值告知是否调用正确,我们增加程序的鲁棒性,提示在哪里出错并结束运行,此外,把变量的定义放在最前面。

#include 
#include 
#include 
#include 
#include 
#include 
  
void error_handling(char *message);
int main(int argc, char *argv[])
{
     
    int serv_sock;
    int clnt_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;
  
    char message[] = "Hello world!";
  	//调用格式是传入的可执行文件以及端口号
    if (argc != 2)//传入两个参数正确,其他结束
    {
     
        printf("Usage: %s \n", argv[0]);
        exit(1);
    }
  	//创建套接字socket()
    serv_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)					//新增
        error_handling("sock() error");
        
  	//分配ip地址与端口号
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));//第二个参数就是端口号
    if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");		//新增
        
  	//进入等待连接请求状态listen()
    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");	//新增
        
  	//受理客户端请求accept()
    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
    if (clnt_sock == -1)					//新增
        error_handling("accept() error");
        
  	//write与close
    write(clnt_sock, message, sizeof(message));
    close(clnt_sock);
    close(serv_sock);
    return 0;
}
  
void error_handling(char *message)	//新增处理函数
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(3)客户端

TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第3张图片

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
     
    char message[30];
  
    //创建套接字socket()
    int sock;
    sock = socket(AF_INET, SOCK_STREAM, 0);
    
    //connect()
	struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));  
    connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr);
    
    //read()
  	int str_len;
    str_len = read(sock, message, sizeof(message) - 1);
    printf("Message from server: %s\n", message);
    
    //close()
    close(sock);
    return 0;
}

(4)修改的客户端

同样对客户端也进行修改

#include 
#include 
#include 
#include 
#include 
#include 

void error_handling(char *message);
  
int main(int argc, char *argv[])
{
     
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;
  //调用格式是传入的可执行文件、ip地址、端口号
    if (argc != 3)/传入三个参数正确,其他结束
    {
     
        printf("Usage: %s  \n", argv[0]);
        exit(1);
    }
    
  	//创建套接字socket()
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("sock() error");
        
  	//connect()
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);//第二个参数是ip地址
    serv_addr.sin_port = htons(atoi(argv[2]));//第三个参数是端口号
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("connect() error!");
        
  	//read()
    str_len = read(sock, message, sizeof(message) - 1);
    if (str_len == -1)
        error_handling("read() error!");
    printf("Message from server: %s\n", message);
    
    //close()
    close(sock);
    return 0;
}
  
void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(5)运行

打开两个终端,linux命令如下

//新建hello_server.c文件
touch hello_server.c
//编辑(敲代码)
vim hello_server.c
//编译
gcc hello_server.c -o hserver
//运行
./hserver 9190 
//新建hello_client.c文件
touch hello_client.c
//编辑(敲代码)
vim client_server.c
//编译
gcc client_server.c -o hclient
//运行
./hclient 127.0.0.1 9190

客户端输出表示成功
在这里插入图片描述
在这里插入图片描述

3.实现回声服务器端与客户端

回声服务器端与客户端顾名思义,服务器端将客户端传输的字符串数据原封不动地传回客户端,就像回声一样。上面讨论的hello_word服务器端处理完一个客户端连接请求退出,那么该怎么修改呢、最好的办法是插入循环语句反复调用accept函数。
TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第4张图片
基本运作方式

  • 服务器端一次只能与一个客户端服务
  • 服务器端依次向5个客户端服务并退出
  • 客户端将接收用户输入的字符串,发送到服务器端,服务器端再传回客户端
  • 客户端输入Q或q停止

(1)服务器端实现

新增循环语句实现上图的流程

	#define BUF_SIZE 1024
	char message[BUF_SIZE];
	
	clnt_adr_sz = sizeof(clnt_adr);
	for(int i = 0;i < 5;i++)//调用5次 
	{
     
		//accept() 注意第二参数是客户端不是服务端、第三个参数是二参结构体长度的指针
		clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz); 
		if(clnt_sock == -1)
			error_handling("accept() error");
		else
			printf("Connect client %d \n",i+1);//输出这是第几个连接
			
		//read()、write() 
		while((str_len = read(clnt_sock,message,BUF_SIZE)) != 0)
			write(clnt_sock,message,str_len);//回写 
		//close client
		close(clnt_sock);		
	}
	//close server
	close(serv_sock);

完整echo_server.c

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
  
void error_handling(char *message);
int main(int argc,char* argv[])
{
     
	int serv_sock,clnt_sock;
	int str_len;//读取的字节数
	char message[BUF_SIZE];
	
	struct sockaddr_in serv_adr,clnt_adr ;
	socklen_t clnt_adr_sz;	
	if(argc != 2)
	{
     
		printf("Usage : %s \n",argv[0]);
		exit(1);
	}
	
	serv_sock = socket(PF_INET,SOCK_STREAM,0);
	if(serv_sock == -1)
		error_handling("socket() error");
		
	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_adr.sin_port = htons(atoi(argv[1]));//端口号 
	
	if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1) 
		error_handling("bind() error");
	if(listen(serv_sock,5) == -1)
		error_handling("listen() error");
	
	clnt_adr_sz = sizeof(clnt_adr);
	for(int i = 0;i < 5;i++)//调用5次 
	{
     
		//accept() 
		clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz); 
		if(clnt_sock == -1)
			error_handling("accept() error");
		else
			printf("Connect client %d \n",i+1);//输出这是第几个连接
		//read()、write() 
		while((str_len = read(clnt_sock,message,BUF_SIZE)) != 0)
			write(clnt_sock,message,str_len);//回写 
		close(clnt_sock);		
	}
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2)客户端实现

客户端可以发送无限次数的消息,直到输入Q、q退出,那么我们需要用while(1)循环完成,输入Q、q再break就行。但是发送无限次数不等于一次发送无线长度的消息,发送长度需要小于BUF_SIZE。

	#define BUF_SIZE 1024
	char message[BUF_SIZE];

	while(1)
	{
     
		fputs("Input Message(Q to quit): ",stdout); 	//输出 
		fgets(message,BUF_SIZE,stdin);					//输入
		
		//输入q、Q直接退出					 
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		write(sock,message,strlen(message));
		str_len = read(sock,message,BUF_SIZE - 1);	//去掉字符串结束符 
		message[str_len] = 0;						//字符串结束符位置覆盖掉 
		printf("Message from server: %s",message);	//回显发送的信息		
	}	
	close(sock);

完整echo_client.c

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
  
void error_handling(char *message);
int main(int argc,char* argv[])
{
     
	int sock;
	int str_len;//读取的字节数
	char message[BUF_SIZE];
	
	struct sockaddr_in serv_adr;

	if(argc != 3)
	{
     
		printf("Usage : %s  \n",argv[0]);
		exit(1);
	}
	
	serv_sock = socket(PF_INET,SOCK_STREAM,0);
	if(serv_sock == -1)
		error_handling("socket() error");
		
	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));//端口号 
	
	if(connect(sock,(struct sockaddr_in*)&serv_adr,sizeof(serv_adr)) == -1)
		error_handling("connect() error");
	else
		puts("connected......"); 
		
	while(1)
	{
     
		fputs("Input Message(Q to quit): ",stdout); 	//输出 
		fgets(message,BUF_SIZE,stdin);					//输入
		
		//输入q、Q直接退出					 
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		write(sock,message,strlen(message));
		str_len = read(sock,message,BUF_SIZE - 1);	//去掉字符串结束符 
		message[str_len] = 0;						//字符串结束符位置覆盖掉 
		printf("Message from server: %s",message);	//回显发送的信息 
		
	}	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(3)运行

先运行服务器端,再打开客户端。不断发送消息再退出,可以看到服务器端会显示已经连接的次数,第五次结束后程序退出
在这里插入图片描述
TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第5张图片
TCP/IP网络编程笔记Chapter I -4实现一个简单TCP服务器端与客户端_第6张图片

(4)修改客户端

echo_client.c仅仅只调用一次read函数,而TCP是不存在数据边界的。如果数据量太大,客户端可能未收到全部数据包时就调用read()函数,因此我们可以改成循环调用read()函数。

while(1)
	{
     
		fputs("Input Message(Q to quit): ",stdout);
		fgets(message,BUF_SIZE,stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		str_len=write(sock,message,strlen(message));
		recv_len = 0;
		while(recv_len < str_len)
		{
     
			recv_cnt = read(sock,&message[recv_len],BUF_SIZE - 1);
			if(recv_cnt == -1)
				error_handling("read() error");
			recv_len += recv_cnt;

		}
		message[str_len] = 0;
		printf("Message from server: %s",message);
		
	}

记录写入的字节数,如果已经read()的字节小于写入的字节就继续read()。完整代码如下,在数据量不大时运行结果和未修改的客户端结果相同,再次不赘述

#include
#include
#include
#include
#include
#include
#define BUF_SIZE 1024
  
void error_handling(char *message);
int main(int argc,char* argv[])
{
     
	int sock;
	int str_len,recv_len,recv_cnt;
	char message[BUF_SIZE];
	
	struct sockaddr_in serv_adr;

	if(argc != 3)
	{
     
		printf("Usage : %s  \n",argv[0]);
		exit(1);
	}
	
	sock = socket(PF_INET,SOCK_STREAM,0);
	if(sock == -1)
		error_handling("socket() error");
		
	memset(&serv_adr,0,sizeof(serv_adr));
	serv_adr.sin_family = AF_INET;
	serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_adr.sin_port = htons(atoi(argv[2]));//端口号 
	
	if(connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
		error_handling("connect() error");
	else
		puts("connected......");
	while(1)
	{
     
		fputs("Input Message(Q to quit): ",stdout);
		fgets(message,BUF_SIZE,stdin);
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
			break;
		str_len=write(sock,message,strlen(message));
		recv_len = 0;
		while(recv_len < str_len)
		{
     
			recv_cnt = read(sock,&message[recv_len],BUF_SIZE - 1);
			if(recv_cnt == -1)
				error_handling("read() error");
			recv_len += recv_cnt;

		}
		message[str_len] = 0;
		printf("Message from server: %s",message);
		
	}
	
	close(sock);
	return 0;
}

void error_handling(char *message)
{
     
    fputs(message, stderr);
    fputc('\n', stderr);
	exit(1);
}

你可能感兴趣的:(TCP/IP网络编程,linux,socket,c++)