实现TCP通信的客户端程序 【linux】(zzz)
第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;
}
运行结果为:
服务器端打印出来了客户端的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;
}
上一篇: 佳能Canon喷墨打印机常见故障及解决方案工程师维修手册
下一篇: 激光切管套料排版编程软件
推荐阅读
-
C语言学习之Linux下TCP服务器与客户端的实现
-
使用C#实现基于TCP和UDP协议的网络通信程序的基本示例
-
网络编程概念、UDP通信程序和TCP通信程序的通信原理及实现程序
-
实现UDP通信的例子程序【linux】(zzzc)
-
linux网络编程之用socket实现简单客户端和服务端的通信(基于TCP)
-
实现TCP的 客户端和服务器端的 即时聊天通信
-
利用Qt/C++在腾讯云/阿里云服务器搭建TCP/IP协议实现网络通信以及Qt在linux下的安装和程序打包踩坑(详解)
-
一个简单的基于TCP通信的服务器端与客户端程序
-
Java实现简易TCP客户端、服务器端通信程序
-
基于Java的Socket类Tcp网络编程实现实时聊天互动程序(二):Tcp通信的过程及代码编写