《UNIX Network Programming Volume1: The Socket Networking API, Third Edition》
W.Richard Stevens / Bill Fenner / Andrew M.Rudoff
- 一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的(单个进程/线程就得处理所有客户)。
UDP套接字调用connect(不同于TCP):没有三路握手过程,内核只是检查是否存在错误,记录对端IP地址和端口号。
- 未连接UDP套接字(unconnected UDP socket):新创建的UDP套接字默认如此。
- 已连接UDP套接字(connected UDP socket):对创建的UDP套接字调用connect的结果。
未连接UDP套接字常使用
recvfrom
和sendto
,已连接UDP套接字常使用read
和write
。- 一个已连接UDP套接字能且仅能与一个对端IP地址交换数据报。
- 已连接UDP套接字引发的异步错误会返回给它们的进程,而未连接UDP套接字不接收任何异步错误。
- 对于TCP套接字,connect只能调用一次。对于UDP,再次调用connect可以指定新的IP地址和端口号或者断开套接字(把地址族设为
AF_UNSPEC
)。 - TCP端口与UDP端口相互独立。
- UDP缺乏流量控制,如果套接字缓冲区时会丢弃数据。
recvfrom和sendto
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen);
前三个参数:等同于read和write函数,而且sendto长度为0的数据报或者recvfrom返回0值都是可行的。
flag
参数:与recv/send类型函数相关。
sendto
的后两个参数:指向一个含有数据报接收者的协议地址(类似于connect)。
recvfrom
的后两个参数:指向一个数据报发送者的协议地址(类似于accept)。
返回值:所接收数据报中的用户数据量大小。
未连接UDP套接字客户/迭代服务器
使用UDP重写上篇文章” TCP客户/服务器“中的回射服务例子如下:
udpcli.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERV_PORT 8755
#define MAXLINE 32
#define error_exit(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
void dg_cli(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
int n;
char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = malloc(servlen);
while (fgets(sendline, MAXLINE, fp) != NULL) {
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
len = servlen;
n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
printf("reply from %s:%d : ",
inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
ntohs(((struct sockaddr_in *)preply_addr)->sin_port));
if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0)
printf("(ignored)");
recvline[n] = 0;
fputs(recvline, stdout);
}
free(preply_addr);
}
int main(int argc, char **argv) {
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
error_exit("usage: udpcli <IPaddress>");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
exit(0);
}
udpserv.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERV_PORT 8755
#define MAXLINE 32
#define error_exit(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
void dg_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen) {