典型UDP客户端和服务端结构与代码调用顺序:
recvfrom和sendto函数:
验证接收到的响应:
对于UDP,其不像TCP是基于有连接的,这样就有一个问题,sendto之后,recvfrom回来的数据可能不是从sendto目的地址回复的消息,这样就需要验证接收到的响应,提供如下方法供参考:
- 比较sendto目的地址和recvfrom接收地址,查看是否一样。不过这样做有风险,如果服务器是多ip地址的,其每次回响应的地址可能不一样,所以产生如下俩方法;
- 客户端推荐做法:客户端通过recvfrom回来的ip地址,通过DNS来查找其域名,用域名来判断而不是用IP地址;
- 服务端推荐做法:服务器端,为每个IP地址创建一个socket,并通过bind绑定到对应socket种,然后加入到select中,再从可读的套接字中给出应答。
UDP的connet函数:
思考一个问题,如果服务器没启动,客户端sendto发送一个数据,结果会是什么呢?
结论是,sendto成功返回,如果客户端还同时调用了recvfrom,则将永远堵塞在recvfrom函数(当然可以设超时),同时通过tcpdump还可以看到,服务端返回icmp port unreachable错误消息,但是这个消息并没有通过sendto和recvfrom函数返回给用户进程,换句话说,用户并不知道服务端返回了icmp错误。怎么办呢,udp的connet函数可以搞定这些。
对于udp socket调用connect,称之为已连接UDP socket,其余未连接UDP socket区别如下:
- 不使用sendto,而使用write或send,因为在connect中,已经指定目的端IP地址;
- 不应用recvfrom,而使用read或recv或recvmsg,注意,如果源地址不是connect连接的目的地址,是不会回馈到该套接字的,这正好由内核帮我们完成了验证接收到的响应;
- 由已连接的UDP套接字引发的异步错误,会返回给他们所在的进程,这样就很好的解决了上述问题;
- 在调用connect,确定目的IP和port之外,同时还会通过目的地址,查找路由表,确定本地地址,connect之后调用getsockname可以获取到。
总结:UDP客户或服务进程,仅在使用自己的UDP套接字与确定的唯一对端进行通信时,才会调用connect,当然,一般UDP客户端会用connect多一点。同时如果采用connect,其性能也会得到提升,因为对于sendto来讲,其发送数据前和后,需要连接套接字、断开套接字,如果connect之后,这两步就省下了。
udp的流量控制
通过sendto函数,其返回值是会返回一个已发送字节数,但是这个仅仅表示用户进程已经写入到发送缓冲区的数据,并不是真正发送给目的端的字节数,所以如何控制udp的发送速度是一个值得考虑的问题。
例子:
下面给出 已连接UDP套接字的客户端代码 和 通过采用select的TCP和UDP混合的服务端代码。
udp client:
/* sockclnt.c*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /*for struct sockaddr_in*/
#define SVR_IP "127.0.0.1"
#define SVR_PORT 1234
#define BUFSIZE 255
int main()
{
int ret = 0;
int sockfd = 0;
struct sockaddr_in svr_addr;
memset(&svr_addr, 0, sizeof(struct sockaddr_in));
// create client socket fd
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket failed");
exit(1);
}
// connet to server
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(SVR_PORT);
svr_addr.sin_addr.s_addr = inet_addr(SVR_IP);
ret = connect(sockfd, (struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if (ret == -1) {
perror("connect failed");
exit(1);
}
int i = 0;
for (i = 0; i < 150; i++)
{
char * msg = "udp hello server....";
printf("client send msg: %s\n", msg);
if((send(sockfd, msg, strlen(msg), 0))<0)
{
perror("send()");
}
char recv_buf[BUFSIZE] = {0};
int recv_len = recv(sockfd, recv_buf, sizeof(recv_buf), 0);
printf("client recv msg: %s%d\n", recv_buf, recv_len);
sleep(1);
}
close(sockfd);
}
tcp client:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /*for struct sockaddr_in*/
#define SVR_IP "127.0.0.1"
#define SVR_PORT 1234
#define BUFSIZE 255
int main()
{
int ret = 0;
int sockfd = 0;
struct sockaddr_in svr_addr;
memset(&svr_addr, 0, sizeof(struct sockaddr_in));
// create client socket fd
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket failed");
exit(1);
}
// connet to server
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(SVR_PORT);
svr_addr.sin_addr.s_addr = inet_addr(SVR_IP);
ret = connect(sockfd, (struct sockaddr *)&svr_addr, sizeof(struct sockaddr_in));
if (ret == -1) {
perror("connect failed");
exit(1);
}
int i = 0;
for (i = 0; i < 100; i++)
{
char * msg = "tcp hello server....";
printf("client send msg: %s\n", msg);
if((send(sockfd, msg, strlen(msg), 0))<0)
{
perror("send()");
}
char recv_buf[BUFSIZE] = {0};
int recv_len = recv(sockfd, recv_buf, sizeof(recv_buf), 0);
printf("client recv msg: %s%d\n", recv_buf, recv_len);
sleep(2);
}
close(sockfd);
}
udp tcp select server
/******************************************************************************
Copyright (C), 2001-2011, DCN Co., Ltd.
******************************************************************************
File Name : main.c
Version : Initial Draft
Author : Dong Shen
Created : 2012/9/19
Last Modified :
Description : transcode main
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/queue.h>
#define MYPORT 1234 // the port users will be connecting to
#define BACKLOG 512 // how many pending connections queue will hold
#define BUF_SIZE 1024
#define MAX_PATH_LEN 255
#ifndef bool
#define bool int
#endif
#define FALSE 0
#define TRUE 1
typedef struct fd_a_t
{
int fd_a;
bool need_write;
char * ret_buf;
int ret_buf_len;
TAILQ_ENTRY(fd_a_t) fd_a_node;
}fd_a_t;
typedef TAILQ_HEAD(fd_a_list_t_, fd_a_t) fd_a_list_t;
void free_node(fd_a_t * cur_fd_node)
{
if (NULL == cur_fd_node)
{
return;
}
if (cur_fd_node->ret_buf != NULL)
{
free(cur_fd_node->ret_buf);
cur_fd_node->ret_buf = NULL;
}
free(cur_fd_node);
cur_fd_node = NULL;
return;
}
int main(void)
{
int ret = 0;
int listen_fd = 0; // listen on listen_fd
int new_fd = 0; // new connection on new_fd
int udp_fd = 0; // udp socket fd
struct sockaddr_in server_addr; // server address information
struct sockaddr_in client_addr; // connector's address information
memset(&server_addr, 0, sizeof(struct sockaddr_in));
memset(&client_addr, 0, sizeof(struct sockaddr_in));
// create tcp listen socket, bind, listen
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listen_fd)
{
perror("socket");
return -1;
}
int yes = 1;
ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (-1 == ret)
{
perror("setsockopt");
return -1;
}
server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
ret = bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (-1 == ret)
{
perror("bind");
return -1;
}
ret = listen(listen_fd, BACKLOG);
if (-1 == ret)
{
perror("listen");
return -1;
}
udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == udp_fd)
{
perror("socket");
return -1;
}
bzero(&server_addr, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));
ret = bind(udp_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (-1 == ret)
{
perror("bind");
return -1;
}
int maxsock = 0;
int conn_amount = 0;
fd_a_list_t fd_a_hd;
TAILQ_INIT(&fd_a_hd);
fd_set read_fdsr;
fd_set write_fdsr;
// timeout setting
// struct timeval tv;
while (1)
{
// tv.tv_sec = 30;
// tv.tv_usec = 0;
// initialize file descriptor set
FD_ZERO(&read_fdsr);
FD_ZERO(&write_fdsr);
FD_SET(listen_fd, &read_fdsr);
FD_SET(udp_fd, &read_fdsr);
maxsock = listen_fd > udp_fd ? listen_fd : udp_fd;
fd_a_t * cur_fd_node = TAILQ_FIRST(&fd_a_hd);
while (cur_fd_node != NULL)
{
FD_SET(cur_fd_node->fd_a, &read_fdsr);
FD_SET(cur_fd_node->fd_a, &write_fdsr);
if (cur_fd_node->fd_a > maxsock)
{
maxsock = cur_fd_node->fd_a;
}
cur_fd_node = TAILQ_NEXT(cur_fd_node, fd_a_node);
}
// select read write socket fd, read_fdsr.
ret = select(maxsock + 1, &read_fdsr, &write_fdsr, NULL, NULL);
if (ret < 0)
{
perror("select");
break;
}
else if (ret == 0)
{
printf("select timeout\n");
continue;
}
// check whether a new connection comes
if (FD_ISSET(listen_fd, &read_fdsr))
{
int sin_size = sizeof(client_addr);
new_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &sin_size);
if (new_fd <= 0)
{
perror("accept");
continue;
}
// add to fd queue
if (conn_amount < BACKLOG)
{
// create new fd node, insert into fd list
fd_a_t * new_fd_a_node = calloc(1, sizeof(fd_a_t));
new_fd_a_node->fd_a = new_fd;
TAILQ_INSERT_TAIL(&fd_a_hd, new_fd_a_node, fd_a_node);
conn_amount++;
printf("new connection client[%d] %s:%d\n", new_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
else
{
printf("max connections arrive, exit\n");
send(new_fd, "bye", 4, 0);
close(new_fd);
continue;
}
}
if (FD_ISSET(udp_fd, &read_fdsr))
{
char msg[BUF_SIZE] = {0};
struct sockaddr_in cliaddr;
socklen_t len = sizeof(struct sockaddr_in);
ssize_t n = recvfrom(udp_fd, msg, BUF_SIZE, 0, (struct sockaddr *)&cliaddr, &len);
printf("udp recv: %s\n", msg);
printf("udp send: %s\n", msg);
sendto(udp_fd, msg, n, 0, (struct sockaddr *)&cliaddr, len);
}
// check every fd in the set
cur_fd_node = TAILQ_FIRST(&fd_a_hd);
while(cur_fd_node != NULL)
{
if (FD_ISSET(cur_fd_node->fd_a, &read_fdsr))
{
char recv_buf[BUF_SIZE] = {0};
int ret_len = recv(cur_fd_node->fd_a, recv_buf, sizeof(recv_buf), 0);
if (ret_len <= 0) // client close
{
printf("client[%d] close\n", cur_fd_node->fd_a);
// close socket, clear fd_set
close(cur_fd_node->fd_a);
// remove fd_node from fd_list
fd_a_t * next_fd_node = TAILQ_NEXT(cur_fd_node, fd_a_node);
TAILQ_REMOVE(&fd_a_hd, cur_fd_node, fd_a_node);
free_node(cur_fd_node);
cur_fd_node = next_fd_node;
conn_amount--;
continue;
}
else // receive data
{
printf("tcp recv: %s\n", recv_buf);
cur_fd_node->need_write = TRUE;
cur_fd_node->ret_buf_len = ret_len;
cur_fd_node->ret_buf = calloc(1, strlen(recv_buf) + 1);
strcpy(cur_fd_node->ret_buf, recv_buf);
}
}
if (FD_ISSET(cur_fd_node->fd_a, &write_fdsr))
{
if (TRUE == cur_fd_node->need_write)
{
printf("tcp send %s\n", cur_fd_node->ret_buf);
send(cur_fd_node->fd_a, cur_fd_node->ret_buf, cur_fd_node->ret_buf_len, 0);
cur_fd_node->need_write = FALSE;
}
}
cur_fd_node = TAILQ_NEXT(cur_fd_node, fd_a_node);
}
}
fd_a_t * cur_fd_node = NULL;
while ((cur_fd_node = TAILQ_FIRST(&fd_a_hd)) != NULL)
{
printf("client[%d] close\n", cur_fd_node->fd_a);
// close socket, clear fd_set
close(cur_fd_node->fd_a);
// remove fd_node from fd_list
TAILQ_REMOVE(&fd_a_hd, cur_fd_node, fd_a_node);
free_node(cur_fd_node);
conn_amount--;
}
return 1;
}