欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现TCP通信的客户端程序 【linux】(zzz)

程序员文章站 2022-06-06 08:40:29
...

第1步:调用socket创建套接字文件,返回套接字文件描述符,指定使用TCP协议。

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

参数如何设置?

domain

指定为PF_INET,表示使用的IPV4是TCP/IP协议族

type

指定为SOCK_STREAM,表示使用的是面向连接的可靠传输协议
在TCP/IP协议族中,只有TCP是面向连接的可靠传输协议,所以使用的是TCP协议。

protocol

0,不指定协议号。

函数调用成功就“返回套接字文件描述符”,在客户端程序中,socket返回“套接字文件描述符”,直接用于通信。

第2步:调用connect主动向服务器发起三次握手,进行连接。

客户端调用connect主动发起连接请求,服务器调用accept被动接收请求,三次握手ok,连接就成功了。

这三次握手是由TCP通信协议自动完成,我们只需要调用connect和accept这两个函数接口即可。

连接成功后,服务器端的accept会返回专门与该客户通信的描述符,而客户端则直接使用socket返回套接字文件描述符来通信。

为什么客户端程序没有bind和listen

为什么客户端程序没有bind?

我们之前说过,bind的目的是为了让套接字文件使用固定的ip和端口号来通信,但是只有TCP服务器有使用固定ip和端口的需求,但是于客户端来说,不需要固定的IP和端口,所以不需要调用bind函数来绑定,客户端只需使用自动分配的IP和端口即可。

我们前面说过,客户端使用自动分配的端口号时,端口号的分配范围为49152~65535。

如果客户程序非要bind绑定可以吗?
当然可以,读者可以自己试试,尝试给客户端也绑定固定的ip和端口,不过对于客户端
来说,没有什么意义。

为什么客户端程序没有listen?

对客户端程序来说,客户端永远都是主动向服务器发起连接请求的,没有被动监听别人连接的需求,因此根本就不需要所谓的被动文件描述符,因此根本就用不到listen函数。

connect函数

函数原型

#include <sys/types.h>         
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能

向服务器主动发起连接请求

返回值

成功返回0,失败返回-1,ernno被设置

参数

  • sockfd:socket所返回的套接字文件描述符
  • addrlen:参数2所指定的结构体变量的大小
  • addr:用于设置你所要连接服务器的IP和端口。

如果只是纯粹的局域网内部通信的话,ip就是局域网IP,但是如果是跨网通信的话,IP必须是服务器所在路由器的公网IP。

为了方便操作,在应用层我们还是使用struct sockaddr_in来设置,然后传递给connect时,
再强制换为struct sockaddr。

struct sockaddr_in seraddr;
										
addr.sin_family = AF_INET;    
addr.sin_port		= htons(5006);//服务器程序的端口
addr.sin_addr.s_addr = inet_addr("192.168.1.105");//服务器的ip地址
cfd = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));										
		
struct sockaddr_in {
sa_family_t			sin_family;
__be16				sin_port;
struct in_addr	sin_addr;
/* 填补相比struct sockaddr所缺的字节数,保障强制转换不要出错 */
unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
};

第3步:调用read(recv)和write(send)收发数据。

同样的,由于在建立TCP连接时,客户端的TCP协议会自动记录下服务器的ip和端口,调用这些函数收发数据时,我们不需要重新指定服务器的ip和端口,因为TCP通信有记录。

主线程发送数据,次线程接受数据。客户端和服务器端在进行通信的时候,通信的结构体必须相同。(包括结构体成员的顺序)
代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#define SPROT 5006
#define SIP "192.168.31.162"

//封装发送的应用层数据:学生信息
typedef struct _data
{
	unsigned int stu_num;		//学号	
	char stu_name[50];	//姓名
}Data;

void  print_err(char * str,int line,int err_no)//出错处理函数
{
	printf("%d,%s: %s\n",line,str,strerror(err_no));
	exit(-1);
}

int sockfd = -1;

void* pth_fun(void *pth_arg)   //次线程用来接收服务器端发送过来的数据
{
	int ret = 0;
	Data stu_data = {0};
	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));
		ret = recv(sockfd,&stu_data,sizeof(stu_data),0);
		if(ret > 0)
		{

			printf("student number:%d\n",ntohl(stu_data.stu_num));
			printf("student name:%s\n",stu_data.stu_name);
		}
		else if(-1 == ret)	
			print_err("recv fail",__LINE__,errno);
	}
}


int main()
{
	int ret = 0;
	/*创建套接字文件并指定使用TCP协议*/
	/*socket返回“套接字文件描述符”,直接用于通信*/
	sockfd = socket(PF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
		print_err("socket fail",__LINE__,errno);

	/*调用connect 函数  向服务器主动请求连接*/
	struct sockaddr_in seraddr = {0}; 	   //用于存放要请求连接的那个服务器的ip和端口
	seraddr.sin_family = AF_INET;		   //地址族,指定ip地址格式
	seraddr.sin_port = htons(SPROT);    	   //请求连接的服务器程序端口
	seraddr.sin_addr.s_addr = inet_addr(SIP);  //请求连接的服务器的ip地址,如果跨网通信就是服务器的公网ip
	ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(-1 == sockfd)
		print_err("connect fail",__LINE__,errno);
	
	pthread_t tid;
	ret = pthread_create(&tid,NULL,pth_fun,NULL);
	if(-1 == ret)
		print_err("pthread_create fail",__LINE__,ret);
		
	
	//主线程发送数据
	Data stu_data = {0};
	int tmp_num = 0; 
	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));

		//封装学生学号
		printf("input student number\n");
		scanf("%d",&tmp_num);
		stu_data.stu_num = htonl(tmp_num);
		
		//封装学生名字
		printf("input student name\n");
		scanf("%s",stu_data.stu_name);

		ret = send(sockfd,(void *)&stu_data,sizeof(stu_data),0);
		if(-1 == sockfd)
			print_err("send fail",__LINE__,errno);
	}

	return 0;
}

运行结果为:
实现TCP通信的客户端程序	【linux】(zzz)

实现TCP通信的客户端程序	【linux】(zzz)

服务器端打印出来了客户端的ip和端口号,说明上面客户端和服务器的连接成功了,并且我们也已经发送了数据。

客户端44132是自动分配的端口号
一般来说分配客户端的端口在:49152~65535范围之间。

第4步:调用close或者shutdown关闭连接。

代码演示:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#define SPROT 5006
#define SIP "192.168.31.162"

//封装发送的应用层数据:学生信息
typedef struct _data
{
	unsigned int stu_num;		//学号	
	char stu_name[50];	//姓名
}Data;

void  print_err(char * str,int line,int err_no)//出错处理函数
{
	printf("%d,%s: %s\n",line,str,strerror(err_no));
	exit(-1);
}

int sockfd = -1;

void* pth_fun(void *pth_arg)   //次线程用来接收服务器端发送过来的数据
{
	int ret = 0;
	Data stu_data = {0};
	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));
		ret = recv(sockfd,&stu_data,sizeof(stu_data),0);
		if(ret > 0)
		{

			printf("student number:%d\n",ntohl(stu_data.stu_num));
			printf("student name:%s\n",stu_data.stu_name);
		}
		else if(-1 == ret)	
			print_err("recv fail",__LINE__,errno);
	}
}


void signal_fun(int signo)
{
	if(SIGINT == signo)
	{
		/*断开连接*/
		//close(sockfd);
		shutdown(sockfd,SHUT_RDWR);
		exit(0);
	}
}


int main()
{
	int ret = 0;
	//调用信号处理函数用于断开连接
	signal(SIGINT,signal_fun);

	/*创建套接字文件并指定使用TCP协议*/
	/*socket返回“套接字文件描述符”,直接用于通信*/
	sockfd = socket(PF_INET,SOCK_STREAM,0);
	if(-1 == sockfd)
		print_err("socket fail",__LINE__,errno);

	/*调用connect 函数  向服务器主动请求连接*/
	struct sockaddr_in seraddr = {0}; 	   //用于存放要请求连接的那个服务器的ip和端口
	seraddr.sin_family = AF_INET;		   //地址族,指定ip地址格式
	seraddr.sin_port = htons(SPROT);    	   //请求连接的服务器程序端口
	seraddr.sin_addr.s_addr = inet_addr(SIP);  //请求连接的服务器的ip地址,如果跨网通信就是服务器的公网ip
	ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));
	if(-1 == sockfd)
		print_err("connect fail",__LINE__,errno);
	
	pthread_t tid;
	ret = pthread_create(&tid,NULL,pth_fun,NULL);
	if(-1 == ret)
		print_err("pthread_create fail",__LINE__,ret);
		
	
	//主线程发送数据
	Data stu_data = {0};
	int tmp_num = 0; 
	while(1)
	{
		bzero(&stu_data,sizeof(stu_data));

		//封装学生学号
		printf("input student number\n");
		scanf("%d",&tmp_num);
		stu_data.stu_num = htonl(tmp_num);
		
		//封装学生名字
		printf("input student name\n");
		scanf("%s",stu_data.stu_name);

		ret = send(sockfd,(void *)&stu_data,sizeof(stu_data),0);
		if(-1 == sockfd)
			print_err("send fail",__LINE__,errno);
	}
	
	return 0;
}