Linux —— TCP/UDP通讯架构搭建

提示:TCP/UDP是目前最常用的网络通讯方式,本文记录两种通讯的服务器及客户端架构搭建


前言

TCP/UDP 都是目前最常用的网络协议,两种协议均基于 IP 地址进行通讯,程序方面两种协议都依靠 socket(套接字)来描述 IP 及端口,分别用于不同的场合。

相比较而言,TCP 采用了三次握手后连接,四次挥手后断开,每一个消息均有回应的方式,可靠性较高,但传输速度较慢。
而 UDP 直接发送消息,效率较高,但可靠性没有保证。


`

一、UDP 通讯架构搭建

相对于TCP而言,UDP 的通讯架构更为简单。从数据发送的角度来讲,只需要指定发送目标地址和端口就可以把数据发送出去,不需要类似 TCP 的握手过程。为了记录相关的函数,本例依然以服务器和客户端的数据交互为构架,主要步骤可以表述如下:
在这里插入图片描述

总体可见服务器和客户端涉及到的主要函数是 sendto 和 recvfrom ,由于 UDP 在数据发送时并没有建立连接,所以数据发送必须采用 sendto 函数来指定服务器的IP以及端口号,而同类的函数 send 只能用于已经建立好连接的 socket,也就是 TCP 中的 socket

1. 相关函数

1.1 socket(2)

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

	// AF_INET 指代了基于 IPV4 通讯
	// SOCK_DGRAM 指代采用 UDP 方式通讯
	// 普通 UDP 通讯下,protocol 可直接设置为 0
	// 返回值整型是一种设备文件,可以用文件相关函数操作
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);

1. 2 bind(2)

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

	// sockaddr 是基本结构体,sockaddr_in 是 IPV4 下采用的结构体
	// sockaddr_in6 是 IPV6 下采用的结构体,
	struct sockaddr_in ser_addr;	
	// 需要与 socket 一致,均为 IPV4 协议
	ser_addr.sin_family = AF_INET;
	// 指定 socket 端口号,需要注意将数值转为网络字节序
	ser_addr.sin_port = htons(7878);
	// 指定 socket IP 地址,需要注意采用 inet_pton() 转换
	// 如果是服务器端,设置为 INADDR_ANY = 接收所有地址发来的消息
	ser_addr.sin_addr.s_addr = INADDR_ANY;
	// 将通讯方式和地址绑定
	int ret = bind(sfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));

1.3 recvfrom(2)

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);

	// 用于存放收到数据的缓存
	char buf[128] = {0};
	// 客户端的 socket,必须也是基于 IPV4,如果不获取客户信息可以写 NULL
	struct sockaddr_in cli_addr;
	// 客户端 socket 长度,需要有初始值,不然程序会报错,可以写 NULL
	socklen_t cli_socklen = sizeof(struct sockaddr_in);
	// 返回值为实际接收的字节数,接收出错返回 -1
	ret = recvfrom(sfd, buf, 128, 0, (struct sockaddr *)&cli_addr, &cli_socklen);

1.4 sendto(2)

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

	// 和 recvfrom 函数基本一致,区别在于发送的目标地址为 const
	ret = sendto(sfd, buf, ret, 0, (struct sockaddr*)&cli_addr, sizeof(struct sockaddr_in));

2. 服务器端代码

以下为服务器单应答模式代码参考

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>

int main(int argc, char *argv[])
{
	int sfd, ret;
	char buf[128] = {0};
	struct sockaddr_in ser_addr, cli_addr;
	socklen_t cli_socklen = sizeof(struct sockaddr_in);

	// AF_INET = IP(V4), SOCK_DGRAM = UDP
	sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sfd == -1)
		printf("socket get error.\n");
	
	// struct sockaddr is root st, sockaddr_in is child st, sockaddr_in6 is child also.
	ser_addr.sin_family = AF_INET;
	
	ser_addr.sin_port = htons(7878);
	// INADDR_ANY = all client ip be listen.
	ser_addr.sin_addr.s_addr = INADDR_ANY;
	ret = bind(sfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
	if (ret == -1)
		printf("bind erro.\n");

	printf("init finish, wait for connect. \n");
	while(1){
		// rcv = bytes
		ret = recvfrom(sfd, buf, 128, 0, (struct sockaddr *)&cli_addr, &cli_socklen);
		if (ret == -1)
			perror("recvfrom: ");
		// deal with msg
		for (int i = 0; i < ret; i++) {
			buf[i] = toupper(buf[i]);
		}
		sendto(sfd, buf, ret, 0, (struct sockaddr*)&cli_addr, sizeof(struct sockaddr_in));
	}
	close(sfd);

	return 0;
}

3. 客户端代码

以下为客户端代码参考

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

int main(int argc, char *argv[])
{
	int sfd, ret = 0;
    char *msg = {"msg: test msg."};
    char buf[128];
    struct sockaddr_in sock_server;
    struct in_addr ser_addr;

	// AF_INET = IP(V4), SOCK_DGRAM = UDP
	sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sfd == -1)
		printf("socket get error.\n");

	sock_server.sin_family = AF_INET;
    sock_server.sin_port = htons(7878);
    if (argv[1] != NULL)
    ret = inet_pton(AF_INET, argv[1], &ser_addr);
    if (ret == 0) {
        printf("input without valid network address.\n");
        return 0;
    }
    if (ret == -1) {
        printf("inet_pton erro.\n");
        return 0;
    }
    sock_server.sin_addr = ser_addr;

    ret = sendto(sfd, msg, 128, 0, (struct sockaddr*)&sock_server, sizeof(struct sockaddr_in));
    if (ret == -1)
        printf("sent bytes error\n");
    else
        printf("sent bytes %d\n", ret);

    ret = recvfrom(sfd, buf, 128, 0, NULL, NULL);
    if (ret == -1)
        printf("receive bytes error\n");
    printf("receive bytes %s\n", buf);

    close(sfd);

    return 0;
}

二、TCP 通讯构架搭建

对于 TCP 而言,连接时需要三次握手,断开时需要四次挥手,自然步骤有所更改,主要表述如下:
在这里插入图片描述
可见客户端的函数除了 UDP 所用的外,还使用了 connect ,该函数就是给服务器发送连接请求,而服务器方面,采用函数 accept 接收客户端发来的连接请求并建立连接登记,最后返回新的客户端连接描述符,用于描述本次连接。listen 函数用于设置 socket 连接中未决连接队列的最大长度。
由于使用新的连接描述符来表述本次连接,针对本次连接的收发即可直接采用read、write 来完成。

1. 相关函数

1.1 listen(2)

int listen(int sockfd, int backlog);

// sock_fd 是已经绑定了本地端口的 socket 描述符,queue_len 是允许处理的最大未决连接队列长度
int ret = listen(sock_fd, queue_len);

1.2 accept(2)

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

// cfd 是建立的新的连接描述符,如果返回 -1 意味着 accept 出现错误
// sfd 绑定的是本机 socket 端口,cli_addr,cli_socklen 是获取到的建立连接的客户端属性
cfd = accept(sfd, (struct sockaddr *)&cli_addr, &cli_socklen);

建立连接后,cfd 就像是一个已经打开的文件描述符,直接读写数据就可以了,当然也可以用系统提供的 send 以及 recv 函数接口

2. 服务器端代码

以下为服务器代码的参考

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>

int main(int argc, char *argv[])
{
	int sfd, cfd, ret;
    char buf[128] = {0};
    struct sockaddr_in ser_addr, cli_addr;
	socklen_t ser_socklen = sizeof(ser_addr);
    socklen_t cli_socklen = sizeof(cli_addr);
        
    // struct sockaddr is root st, sockaddr_in is child st, sockaddr_in6 is child also.
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port = htons(7878);
    // INADDR_ANY = all client ip be listen.
    ser_addr.sin_addr.s_addr = INADDR_ANY;

    int sfd = socket(sock_addr->sa_family, SOCK_STREAM, 0);
    if (sfd == -1) {
        printf("%s in %d: socket get fail. \n", __FUNCTION__, __LINE__);
        return sfd;
    }
    ret = bind(sfd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
	if (ret == -1)
		printf("bind erro.\n");
	
	int ret = listen(sfd, 10);
	if (ret == -1) perror("listen");

    printf("init finish, wait for connect. \n");
    while(1){
		cfd = accept(sfd, (struct sockaddr *)&cli_addr, &cli_socklen);
		if (cfd == -1) perror("accept");
		// rcv = bytes, sfd = udp sfd
        ret = recvfrom(cfd, buf, 128, 0, (struct sockaddr *)&cli_addr, &cli_socklen);
        if (ret == -1)
          	perror("recvfrom");
        // deal with msg
        for (int i = 0; i < strlen(buf); i++) {
            buf[i] = toupper(buf[i]);
        }
        sendto(cfd, buf, ret, 0, (struct sockaddr*)&cli_addr, sizeof(struct sockaddr_in));
        close(cfd);
    }
    close(sfd);

    return 0;
}

3. 客户端代码

以下为客户端代码参考

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

int main(int argc, char *argv[])
{
	int sfd, ret = 0;
    char *msg = {"msg: test msg."};
    char buf[128];
    struct sockaddr_in sock_server;
    struct in_addr ser_addr;

	// AF_INET = IP(V4), SOCK_DGRAM = UDP, SOCK_STREAM = TCP
	sfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sfd == -1)
		printf("socket get error.\n");

	sock_server.sin_family = AF_INET;
    sock_server.sin_port = htons(7878);
    if (argv[1] != NULL)
    ret = inet_pton(AF_INET, argv[1], &ser_addr);
    if (ret == 0) goto err_usage;
    if (ret == -1) goto err_af_no_support;

    sock_server.sin_addr = ser_addr;
    
    ret = connect(sfd, (struct sockaddr *)&sock_server, sizeof(sock_server));
    if (ret == -1) {
        perror("connect fail");
    }

    ret = sendto(sfd, msg, 128, 0, (struct sockaddr*)&sock_server, sizeof(struct sockaddr_in));
    if (ret == -1)
        printf("sent bytes error\n");
    else
        printf("sent bytes %d\n", ret);

    ret = recvfrom(sfd, buf, 128, 0, NULL, NULL);
    if (ret == -1)
        printf("receive bytes error\n");
    printf("receive bytes %s\n", buf);

    close(sfd);
    return 0;

err_usage:
    printf("usage: %s <server ip> \n", argv[0]);
    return 0;
err_af_no_support:
    printf("af no support in inet_pton \n");
    return 0;
}

总结

UDP 单应答模式记录完成, TCP 单应答模式记录完成,算是填了个坑。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值