目录
-
UDP
- SOCK_DGRAM报式套接字
- 全双工,无连接,不可靠(这不是缺点)
- 传输协议就是UDP
- 收发两端,通常client和server端
-
创建 UDP 套接字
-
server端
- socket(2)创建报式套接字
- connect(2) / listen(2) / accept(2)都是与连接相关的,所有报式套接字都用不到
- 被动端,准备好接受包的条件
- bind(2)地址
- recvfrom(2)接受数据包(得到对端地址)
- close(2)关闭套接字
-
-
-
client端(主动端)
- socket(2)创建报式套接字
- 主动端,第一个动作式发送数据包
- bind(2)可以省略的,如果省略了内核会为进程bind一个空闲的地址
- sendto(2)发送数据包
- close(2)关闭套接字
- server和client需要遵循相同的协议
- server的地址(ip+port)
- 数据格式(数据类型)
-
- SOCK_DGRAM报式套接字
-
示例代码
#ifndef __PORT_H__ #define __PORT_H__ #include <stdint.h> #define SERVERIP "x.x.x.x" #define SERVERPORT 1999 #define MSGSIZE 1024 #define handler_error(msg)\ do {perror(msg); exit(EXIT_FAILURE);} while(0) struct __msg{ int8_t id; char msg[MSGSIZE]; }__attribute__((packed)); typedef struct __msg msg_t; #endif
// Date:2023.09.14 19:51:14 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <string.h> #include "port.h" /* 客户端 */ int main(int argc, char *argv[]) { int cnt; int udp_socket; struct sockaddr_in addr_in; msg_t sendbuf; socklen_t addr_len; if (argc < 3) exit(1); // 创建报式套接子 if(-1 == (udp_socket = socket(AF_INET, SOCK_DGRAM, 0))) handler_error("socket()"); // bind可省略 // 发送的数据 sendbuf.id = atoi(argv[1]); strncpy(sendbuf.msg, argv[2], MSGSIZE); // server地址 addr_in.sin_family = AF_INET; addr_in.sin_port = htons(SERVERPORT); addr_in.sin_addr.s_addr = inet_addr(SERVERIP); //inet_aton(SERVERIP, &addr_in.sin_addr); if(-1 == sendto(udp_socket, &sendbuf, sizeof(sendbuf.id)+strlen(sendbuf.msg)+1, 0,\ (struct sockaddr *)&addr_in, sizeof(addr_in))){ close(udp_socket); handler_error("sendto()"); } memset(sendbuf.msg, '\0', MSGSIZE); if(-1 == recvfrom(udp_socket, &sendbuf, sizeof(sendbuf), 0, NULL, NULL)){ close(udp_socket); handler_error("recvfrom()"); } // 请求后得到服务器反馈 printf("%d , %s \n", sendbuf.id, sendbuf.msg); close(udp_socket); return 0; }
// Date:2023.09.14 19:51:14 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <string.h> #include "port.h" /* 服务端 */ int main(void) { int cnt; int udp_socket; struct sockaddr_in addr_in, client_addr; msg_t rcvbuf; socklen_t client_addr_len; if(-1 == (udp_socket = socket(AF_INET, SOCK_DGRAM, 0))) handler_error("socket()"); // 绑定地址 addr_in.sin_family = AF_INET; addr_in.sin_port = htons(SERVERPORT); // 点分十进制ip转换为ip结构体 //addr_in.sin_addr.s_addr = inet_addr(SERVERIP); inet_aton(SERVERIP, &addr_in.sin_addr); if(-1 == bind(udp_socket, (struct sockaddr *)&addr_in, sizeof(addr_in))){ close(udp_socket); handler_error("bind()"); } // 接受client的数据包之前一定要将地址长度赋值 client_addr_len = sizeof(struct sockaddr_in); while(1){ // 等待接受客户端请求 if(-1 == (cnt = recvfrom(udp_socket, &rcvbuf, sizeof(rcvbuf), 0, (struct sockaddr *)&client_addr, &client_addr_len))){ close(udp_socket); handler_error("recvfrom()"); } // 调试语句 printf("[%s][%d] ", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); printf("id:%d , msg: %s\n", rcvbuf.id, rcvbuf.msg); // 向客户端发送数据 rcvbuf.id = 0; strcpy(rcvbuf.msg, "ok"); sendto(udp_socket, &rcvbuf, sizeof(rcvbuf.id) + strlen(rcvbuf.msg) + 1,0, (struct sockaddr *)&client_addr, client_addr_len); } close(udp_socket); return 0; }
需要注意的是
-
在编写 UDP 程序时,你需要关注数据包的大小、分片和重组、丢包处理等问题,因为 UDP 本身不提供这些功能。此外,UDP 也不提供拥塞控制,因此在高负载或不稳定的网络环境中,可能会导致丢包和延迟增加。
-
-
-
UDP支持组播(群发)
- ipv4组播地址:224~239
- 组播就是套接字的选项,使能组播的功能,那么就需要将套接字的组播选项打开
- setsockopt(2)
- 套接字的选项在以下手册中均有:
- man 7 ip(组播选项)
- man 7 socket
- man 7 udp
- man 7 tcp
- 组播选项的级别(level):IPPROTO_IP
- 组播选项的开关:大多数选项需要一个整型变量,如果值为1就是开启,值为0就是关闭
- 选项:
- IP_ADD_MEMBERSHIP 加入多播组
- IP_MULTICAST_IF 将本地设备用于多播
- 抓包器
- wireshark
-
示例代码(组播)
-
#ifndef __GROUP_PROTO_H__
#define __GROUP_PROTO_H__
// 多播组 接受端口
#define GROUP_IP "224.2.3.4"
#define RCV_PORT 1314
// 数据类型
#define MAX_MSG 1024
#endif
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
#include "group_proto.h"
/* 客户端 */
int main(void)
{
int udp_socket;
struct sockaddr_in myaddr; // man 7 ip
struct ip_mreqn imr;
char *buf = NULL;
int cnt;
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udp_socket) {
perror("socket()");
exit(1);
}
// 被动端需要bind地址
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(RCV_PORT);
inet_aton("0.0.0.0"/*INADDR_ANY*/, &myaddr.sin_addr);
if (-1 == bind(udp_socket, (struct sockaddr *)&myaddr, sizeof(myaddr))) {
perror("bind()");
goto ERROR1;
}
// 加入多播组
inet_aton(GROUP_IP, &imr.imr_multiaddr);
// 注意INADDR_ANY不是字符串,是uint32_t整型数
inet_aton("0.0.0.0", &imr.imr_address);
// 虚拟网卡的索引值,索引名转换为值if_nametoindex(3)
imr.imr_ifindex = if_nametoindex("ens33");
if (-1 == setsockopt(udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr))) {
perror("setsockopt()");
goto ERROR1;
}
// 接受消息
buf = malloc(MAX_MSG);
// if error
while (1) {
memset(buf, '\0', MAX_MSG);
cnt = recvfrom(udp_socket, buf, MAX_MSG, 0, NULL, NULL);
if (-1 == cnt) {
perror("recvfrom()");
goto ERROR1;
}
if (strcmp(buf, "bye") == 0)
break;
puts(buf);
}
close(udp_socket);
free(buf);
buf = NULL;
exit(0);
ERROR1:
close(udp_socket);
exit(1);
}
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
#include "group_proto.h"
/* 服务端 */
int main(void)
{
int udp_socket;
struct sockaddr_in snd_addr; // man 7 ip
struct ip_mreqn imr;
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udp_socket) {
perror("socket()");
exit(1);
}
// 将本地设备设置为多播
inet_aton(GROUP_IP, &imr.imr_multiaddr);
imr.imr_address.s_addr = INADDR_ANY;
// inet_aton("0.0.0.0", &imr.imr_address);
// 虚拟网卡的索引值,索引名转换为值if_nametoindex(3)
imr.imr_ifindex = if_nametoindex("ens33");
if (-1 == setsockopt(udp_socket, IPPROTO_IP, IP_MULTICAST_IF, &imr, sizeof(imr))) {
perror("setsockopt()");
goto ERROR1;
}
printf("[%d]debug...\n", __LINE__);
// 发送消息
snd_addr.sin_family = AF_INET;
snd_addr.sin_port = htons(RCV_PORT);
inet_aton(GROUP_IP, &snd_addr.sin_addr);
while (1) {
sendto(udp_socket, "hello", 5, 0, (struct sockaddr *)&snd_addr, sizeof(snd_addr));
sleep(1);
}
close(udp_socket);
exit(0);
ERROR1:
close(udp_socket);
exit(1);
}
-
UDP支持广播
- 广播选项man 7 socket
- SO_BROADCAST
- 广播选项级别:SOL_SOCKET
- 广播地址
- "255.255.255.255"全网广播
- 广播选项man 7 socket
-
TCP
- TCP套接字
- man 7 tcp
- 流式套接字
- 特点:
- 基于连接的,可靠传输
-
创建 TCP 套接字
-
server(被动端)
- socket(2)创建流式套接字
- bind(2)地址
- listen(2)使套接字处于监听状态
- accept(2)等待接受对端的连接请求
- read(2) / write(2)收发消息 send(2) / recv(2)也可以
- close(2)关闭套接字
-
client(主动端)
- socket(2)创建流式套接字
- connect(2)请求与服务器连接
- read(2) / write(2)收发消息 send(2) / recv(2)也可以
- close(2);
-
-
示例代码
#ifndef __TCP_PROT_H__ #define __TCP_PROT_H__ // server地址(ip + port) #define SERVER_IP "x.x.x.x" #define SERVER_PORT 3344 // 对话格式(数据类型) #define MAXMSG 1024 struct msg_st { char msg[MAXMSG]; }__attribute__((packed)); #endif
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include "tcp_proto.h" /* 客户端 */ int main(void) { int tcp_socket; struct sockaddr_in server_addr; struct msg_st rcvbuf; int cnt; // 创建流式套接字 tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (-1 == tcp_socket) { perror("socket()"); exit(1); } // 请求与服务器连接 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); inet_aton(SERVER_IP, &server_addr.sin_addr); if (-1 == connect(tcp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) { perror("connect()"); goto ERROR; } // 接受 memset(rcvbuf.msg, '\0', MAXMSG); cnt = read(tcp_socket, &rcvbuf, MAXMSG); printf("from server rcv msg:%s with %dbytes\n", rcvbuf.msg, cnt); close(tcp_socket); exit(0); ERROR: close(tcp_socket); exit(1); }
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include "tcp_proto.h" /* 服务端 */ int main(void) { int tcp_socket, accepted_socket; struct sockaddr_in myaddr; pid_t pid; // 创建流式套接字 tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (-1 == tcp_socket) { perror("socket()"); exit(1); } // 绑定本地地址 myaddr.sin_family = AF_INET; myaddr.sin_port = htons(SERVER_PORT); myaddr.sin_addr.s_addr = INADDR_ANY; if (-1 == bind(tcp_socket, (struct sockaddr *)&myaddr, sizeof(myaddr))) { perror("bind()"); goto ERROR; } // 套接字处于监听状态 if (-1 == listen(tcp_socket, 200)) { perror("listen()"); goto ERROR; } // sigaction(SIGCHLD, ); SA_NOCHLDWAIT while (1) { // 等待接受客户端的连接请求 accepted_socket = accept(tcp_socket, NULL, NULL); if (-1 == accepted_socket) { perror("accept()"); goto ERROR; } // 成功后accepted_socket就是用于数据交换的套接字 原来的tcp_socket是用于继续接受客户端连接请求的 pid = fork(); if (-1 == pid) { perror("fork()"); goto ERROR; } if (0 == pid) { // 数据交换 用accepted_socket close(tcp_socket); sleep(1); write(accepted_socket, "can you see that?", 17); close(accepted_socket); exit(0); } close(accepted_socket); } close(tcp_socket); exit(0); ERROR: close(tcp_socket); exit(1); }
- TCP套接字
-
需要注意的是
- TCP 提供了可靠的数据传输,包括数据的分段、重组、流量控制和拥塞控制机制。它确保数据按照正确的顺序到达目标,并通过确认机制检测和恢复丢失或损坏的数据。TCP 还实现了流量控制,以避免发送方过载和接收方缓冲区溢出,以及拥塞控制,以优化网络性能和公平共享带宽。
- TCP 提供了可靠的、面向连接的数据传输,适用于对数据完整性和可靠性要求较高的应用场景,如文件传输、网页浏览、电子邮件等
-
三次握手与四次挥手
-
创建连接的三次握手过程
- client向server发起连接请求(第一次握手)
- SYN置1, seq=x
- server收到了client发送的数据包后,会向client发送一个应答,同时发送一个序号(第二次握手)
- ACK=1, ack=x+1, seq=y
- client收到server的应答后,向server发送最后的应答(第三次握手)
- ACK=1, ack = y+1
- 即客户端发送连接请求、服务器响应连接请求并发送确认、客户端再次响应确认
- client向server发起连接请求(第一次握手)
-
-
连接断开四次挥手过程
- client向server发送请求断开的数据包(第一次挥手)
- FIN = 1, seq = m
- server收到了client请求断开的数据包后,会向client端发送应答(第二次挥手)
- ACK = 1, ack = m + 1
- 当server数据交换完成,也不需要通讯的时候,向client发送断开数据包(第三次挥手)
- FIN = 1,seq = n
- client最后向server发送应答(第四次挥手)
- ACK = 1, ack = n + 1
- 即客户端发送关闭连接请求、服务器确认关闭连接请求、服务器发送关闭连接请求、客户端确认关闭连接请求并关闭连接
- client向server发送请求断开的数据包(第一次挥手)
-
IO多路复用在TCP服务器中的应用
- tcp的服务器不是盲目的接收到链接就并发,而是使用select/poll监听到已连接的套接字可读再并发
- 对于套接字文件,有链接请求和有数据到达都是可读事件
- 当client端close套接字,则server端的套接字也是发生了可读事件,并且读到的数据是0.
-
示例代码
#ifndef __TCP_PROT_H__ #define __TCP_PROT_H__ // server地址(ip + port) #define SERVER_IP "x.x.x.x" #define SERVER_PORT 3344 // 对话格式(数据类型) #define MAXMSG 1024 struct msg_st { char msg[MAXMSG]; }__attribute__((packed)); #endif
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include "tcp_proto.h" /* 客户端 */ int main(void) { int tcp_socket; struct sockaddr_in server_addr; struct msg_st sndbuf; int cnt; // 创建流式套接字 tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (-1 == tcp_socket) { perror("socket()"); exit(1); } // 请求与服务器连接 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); inet_aton(SERVER_IP, &server_addr.sin_addr); if (-1 == connect(tcp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr))) { perror("connect()"); goto ERROR; } // 接受 memset(sndbuf.msg, '\0', MAXMSG); strncpy(sndbuf.msg, "this is a test", MAXMSG); write(tcp_socket, &sndbuf, MAXMSG); sleep(2); close(tcp_socket); exit(0); ERROR: close(tcp_socket); exit(1); }
#include <stdio.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include <poll.h> #include "tcp_proto.h" /* 服务端 */ int main(void) { int tcp_socket, accepted_socket; struct sockaddr_in myaddr; pid_t pid; struct pollfd *pfd = NULL; int nfds = 0; struct msg_st rcvbuf; int i; int cnt; // 创建流式套接字 tcp_socket = socket(AF_INET, SOCK_STREAM, 0); if (-1 == tcp_socket) { perror("socket()"); exit(1); } // 绑定本地地址 myaddr.sin_family = AF_INET; myaddr.sin_port = htons(SERVER_PORT); myaddr.sin_addr.s_addr = INADDR_ANY; if (-1 == bind(tcp_socket, (struct sockaddr *)&myaddr, sizeof(myaddr))) { perror("bind()"); goto ERROR; } // 套接字处于监听状态 if (-1 == listen(tcp_socket, 200)) { perror("listen()"); goto ERROR; } // 监听tcp_socket pfd = calloc(1, sizeof(struct pollfd)); if (NULL == pfd) { perror("calloc()"); goto ERROR; } pfd[0].fd = tcp_socket; pfd[0].events = POLLIN; // tcp_socket是否有连接 nfds = 1; while (1) { if (-1 == poll(pfd, nfds, -1)) { if (errno == EINTR) continue; // 被信号打断 perror("poll()"); goto ERROR; } // 监听的事件发生了 for (i = 0; i < nfds; i++) { if (i == 0) { if (pfd[0].fd == tcp_socket && pfd[0].revents & POLLIN) { // 套接字有连接到来了 accepted_socket = accept(tcp_socket, NULL, NULL); if (-1 == accepted_socket) { perror("accept()"); goto ERROR; } printf("**********client connected succefully************\n"); // accepted_socket 就是将来用于数据交换的套接字 // 如果可读了再读 pfd = realloc(pfd, (nfds + 1) * sizeof(struct pollfd)); // if error pfd[nfds].fd = accepted_socket; pfd[nfds].events = POLLIN; // 数据 pfd[nfds].revents = 0; nfds ++; } } else { // 不是客户端的连接请求,而是有数据请求 if (pfd[i].revents & POLLIN) { // 创建子进程/线程 memset(rcvbuf.msg, '\0', MAXMSG); cnt = recv(pfd[i].fd, &rcvbuf, MAXMSG, 0); if (cnt == -1) { perror("recv()"); goto ERROR; } if (cnt == 0) { // c端close() printf("***************888***************\n"); close(pfd[i].fd); pfd[i].events = 0; memmove(pfd + i, pfd + i + 1, (nfds - (i + 1)) * sizeof(struct pollfd)); nfds--; pfd = realloc(pfd, nfds * sizeof(struct pollfd)); i--; // 下一次循环要执行i++ } else { printf("!!!!!!!!!!!recv data!!!!!!!!!!!\n"); // 接受到数据请求,处理请求 // 暂且调试输出 puts(rcvbuf.msg); } } } } } close(tcp_socket); exit(0); ERROR: close(tcp_socket); exit(1); }
UDP与TCP 协议格式![](https://img-blog.csdnimg.cn/59be50197104455bb4bdb770c17d8f02.png)