【C语言】实现linux下的基于C语言的一个简单的TCP客/服 端的通信

一、实例代码

对于基础的好的朋友可以直接取代码,如果想要看详细解析的朋友可以详看下方的解析

TCP服务端代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#define SERVER_PORT 1234

/*
@brief TCP通信服务端
*/
int main()
{
    int Ret = 0;
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sockfd == -1)
	{
		perror("sockfd failed!\n");
		return -1;
	}
 
	//2.绑定端口
	struct sockaddr_in serv_addr;//1.见最下方解析
	memset(&serv_addr, 0, sizeof(serv_addr));//清空结构体
 
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;//记住服务端绑定的是自己的地址,INADDR_ANY相当于inet_addr("0.0.0.0"),表示监听本地所有网卡地址
	serv_addr.sin_port = htons(SERVER_PORT);
 
	Ret = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//绑定本地套接字与端口
    if(Ret == -1)
    {
        perror("bind failed\n");
        close(sockfd);
        return -1;
    }
 
	//3.服务端开始监听:所谓的监听就是等待客户端的连接
	Ret = listen(sockfd, 10);
    if(Ret == -1)
    {
        perror("listen failed\n");
        close(sockfd);
        return -1;
    }
 

	//4.接受客户端的连接请求
	struct sockaddr_in client_addr;//这里直接定义一个地址结构体用于存储客户端的地址信息
	socklen_t c_len = sizeof(client_addr);
 
	int connect_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &c_len);
    if(connect_sockfd == -1)
    {
        perror("connect_sockfd failed!\n");
        close(sockfd);
        return -1;
    }
 
	//5.接收数据
	char recv_buf[1024];//接收数据缓冲区
	while(1)
	{
        memset(recv_buf, 0, sizeof(recv_buf));
		Ret = read(connect_sockfd, recv_buf, sizeof(recv_buf));
        if(Ret == -1)
        {
            perror("read falied\n");
            break;
        }
        else if(Ret == 0)
        {
            printf("file of end\n");
            break;
        }
        else
            printf("%s\n", recv_buf);
	}
 
	//6.关闭套接字
	close(connect_sockfd);
	close(sockfd);
 
	return 0;
}

TCP客户端代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERVER_IP "192.168.220.128"
#define SERVER_PORT 1234

/*
@brief TCP通信客户端
*/
int main()
{
    int Ret = 0;
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("sockfd failed!\n");
		return 0;
	}
 
	//2.连接服务端
	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(SERVER_IP);//服务端地址
	serv_addr.sin_port = htons(SERVER_PORT);
 
	Ret = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//连接服务端
    if(Ret == -1)
    {
        perror("connect failed\n");
        return -1;
    }
 
	//3.发送数据
	char send_buf[1024];//发送数据缓冲区
	while(1)
	{
		memset(send_buf, 0, sizeof(send_buf));//清空之后再接收数据
        scanf("%s", send_buf);
        Ret = write(sockfd, send_buf, strlen(send_buf));
        if(Ret == -1)
        {
            perror("write failed\n");
            break;
        }
	}
 
	//4.关闭套接字
	close(sockfd);
 
	return 0;
}

二、代码效果

        客户端输入数据,按回车表示数据输入完毕,这时候服务端就可以收到数据了,效果如下:

三、接口解析和重点解析

这是最基本的TCP通信协议了,对这份通信代码,能解析的点不多:

1. struct sockaddr_in 

这个结构体是定义在<netinet/in.h>头文件中

大家可以从代码中看出  struct sockaddr_in 这个结构体中的成员就是ip地址簇、端口号、ip地址。

struct sockaddr_in
{
    short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
    unsigned short sin_port;//端口号
    struct in_addr sin_addr;//ip地址
    unsigned char sin_zero[8];//没有实际意义,只是为了跟SOCKADDR结构在内存中对齐
};

 不过有趣的是,这个结构体成员之一的ip地址,是用另外一个结构体定义的,以下是在linux下的对于这个结构体的定义(不同的操作系统这个定义有稍些差异),这里可以看出另定义一个结构体在linux下其实是没什么必要的,不过官方如此定义,那么在原生的时候这个定义肯定有其他的不足的地方,所以才会另外定义一个结构体来存储ip地址,大家可以不用纠结

typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;//ip地址
};

其实很明显可以看出,这个结构体只有一个成员就是 s_addr,也就是ip地址,是一个无符号的32位整型,即 unsigned int  s_addr

2.各个函数的原型以及功能介绍
int socket(int af, int type, int protocol);

功能:创建一个套接字描述符用于通信

参数:

        af:地址族(Address Family),也就是 ip地址类型,常用的有 AF_INET 和 AF_INET6。分别代表ipv4、ipv6
        type:数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM,分别代表TCP数据流传输和UDP数据报传输
        protocol:表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。我们在使用的时候既可以写明,也可以用 0 表示让系统自动推导该用什么协议(因为type已经定义了用TCP数据流方式传输)

返回值

        成功返回可使用的套接字描述符,失败返回-1

服务端:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);

功能:绑定一个 地址+端口 作为自己的传输数据的端点

参数:

        sock:创建的套接字 

        addr:本身为前面介绍的结构体 sockaddr_in,但大家可以看出,这和定义的bind函数的参数类型不匹配啊?是的,不匹配,所以我们在用 bind( )的时候需要将 addr 从 sockaddr_in 强制转换为 sockaddr 类型。并且可以看出 sockaddr 和 sockaddr_in 这两个结构体并没有什么太大的不一样,只不过 sockaddr  将 IP地址和端口 定义在了一起

struct sockaddr
{
        sa_family_t sin_family; //地址族(Address Family),也就是地址类型
        char sa_data[14]; //IP地址和端口号
};

        addrlen:表示 地址结构体的大小

返回值

        成功返回0,失败返回-1

int listen(int sock, int backlog);

功能:监听,将所有请求连接的客户端端点加入队列

参数:

       sock:需要进入监听状态的套接字描述符

       backlog:客户端请求队列的最大长度,即允许一次性有多少个客户端与服务端建立连接,这与内核的缓冲区的大小有关 ,一般建议为1~10之间

返回值

        成功返回0,失败返回-1

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

功能:接受处于监听队列的第一个端点(客户端地址),从而与第一个端点建立连接

参数:

       sock:进入监听状态的套接字描述符

       addr:地址结构体 sockaddr_in,详见bind函数解析,但是这里大家要注意,这里接受来自客户端的连接,这个地址结构体不再是 服务端的地址结构体, 而是客户端的地址结构体

        addrlen:表示 定义的服务端的结构体 sockaddr_in 的大小

返回值

        成功返回一个新的已建立连接的套接字描述符,后续读写都是用这个已经建立好连接的套接字描述符的,而不是一开始建立的用于监听的套接字描述符。失败返回-1

客户端:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

功能:请求连接到第二个参数的网络地址

参数:

        sock:创建的套接字

        addr:服务端地址结构体 sockaddr_in,详见bind函数解析
        addrlen:表示 定义的服务端的结构体 sockaddr_in 的大小

返回值

        成功返回0,失败返回-1

写在后面:对于客户端和服务端来说,建立好连接之后,都可以双向进行收发数据,并没有规定说客户端只能发送,服务端只能接收。​​​​​之所以很多例子都以客户端发送服务端接收为例,是因为在实际项目中,客户端发起连接,建立完毕之后肯定需要发送请求,来告诉服务端,我客户端需要你那边的一些数据,请你(服务端)发给我。​​

        其次,只要明白客户端是去连接服务端的,而服务端只需要绑定自己的地址,等待连接。这样在写代码时就很容易明白,客户端和服务端各自需要哪些操作。

  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是一个简单TCP户端与端的通信示例代码,支持推送指定长度的数据。请注意,此示例代码仅供参考,可能需要根据实际情况进行修改和优化。 服务器代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define BUF_SIZE 100 int main(int argc, char *argv[]) { int server_socket, client_socket; struct sockaddr_in server_addr, client_addr; char buffer[BUF_SIZE]; int str_len, i; if (argc != 2) { printf("Usage: %s <port> \n", argv[0]); exit(1); } server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket == -1) { printf("Server socket creation failed\n"); exit(1); } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(atoi(argv[1])); if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { printf("Server bind failed\n"); exit(1); } if (listen(server_socket, 5) == -1) { printf("Server listen failed\n"); exit(1); } printf("Server is waiting for client\n"); socklen_t client_addr_size = sizeof(client_addr); client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_size); if (client_socket == -1) { printf("Server accept failed\n"); exit(1); } printf("Client connected\n"); while (1) { str_len = read(client_socket, buffer, BUF_SIZE); if (str_len == 0) { break; } printf("Received message: %s\n", buffer); write(client_socket, buffer, str_len); } close(client_socket); close(server_socket); return 0; } ``` 户端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define BUF_SIZE 100 int main(int argc, char *argv[]) { int client_socket; struct sockaddr_in server_addr; char buffer[BUF_SIZE]; int str_len, i; if (argc != 4) { printf("Usage: %s <IP> <port> <length> \n", argv[0]); exit(1); } client_socket = socket(PF_INET, SOCK_STREAM, 0); if (client_socket == -1) { printf("Client socket creation failed\n"); exit(1); } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(atoi(argv[2])); if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { printf("Client connect failed\n"); exit(1); } printf("Connected to server\n"); memset(buffer, 'A', atoi(argv[3])); write(client_socket, buffer, atoi(argv[3])); printf("Sent message: %s\n", buffer); str_len = read(client_socket, buffer, BUF_SIZE); printf("Received message: %s\n", buffer); close(client_socket); return 0; } ``` 运行务端代码: ``` $ ./server 12345 Server is waiting for client ``` 运行户端代码: ``` $ ./client 127.0.0.1 12345 10 Connected to server Sent message: AAAAAAAAAA Received message: AAAAAAAAAA ``` 说明: - 务端代码监听指定端口,并在有户端连接时接受户端发送的消息并回复。 - 户端代码连接指定服务器,并发送指定长度的数据,然后等待服务器回复。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值