提示: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 单应答模式记录完成,算是填了个坑。