标签:
用户数据包协议(User Datagram Protocal
, UDP
),UDP
服务器端和客户端均只需要 1 个套接字。在TCP
中向 10 个客户端提供服务,则除了守门的服务器套接字之外,还需要 10 个服务器端套接字。
UDP
不保存连接状态,因此每次传输数据都要添加目标地址信息。
#include <sys/socket.h>
ssize_t sendto(int sock, void *buf, size_t nbytes, int flag,
struct sockaddr * to, socklen_t addrlen); // 成功时返回传输的字节数,失败时返回-1
~ sock: 用于传输数据的 UDP 套接字文件描述符
~ buf: 待传输数据的缓冲地址值
~ nbytes: 待传输数据的字节长度
~ flag: 可选项参数,没有则传递0
~ to: 目标地址的结构体变量地址
~ addrlen: 地址结构体的长度
接收函数:
#include <sys/socket.h>
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags,
struct sockaddr * from, socklen_t addrlen); // 成功时返回接收的函数,失败时返回-1
~ sock: 用于接受数据的套接字文件描述
~ from: 存有发送端地址信息的结构体变量地址值
基于 UDP
的回声服务器端实例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void err_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
struct sockaddr_in clnt_adr;
socklen_t clnt_adr_sz;
if(argc != 2)
{
printf("Usage : %s <port> \n", argv[0]);
exit(1);
}
serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
if(serv_sock == -1)
err_handling("socket() error");
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
err_handling("shenmecuoel bind() error");
while(1)
{
clnt_adr_sz = sizeof(clnt_adr);
str_len = recvfrom(serv_sock, message, BUF_SIZE, 0,
(struct sockaddr*)&clnt_adr, &clnt_adr_sz);
sendto(serv_sock, message, str_len, 0,
(struct sockaddr*)&clnt_adr, clnt_adr_sz);
}
close(serv_sock);
return 0;
}
void err_handling(char * message)
{
fputs(message, stderr);
fputc(‘\n‘, stderr);
exit(1);
}
基于 UDP
的回声客户端实例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char * message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t addr_sz;
struct sockaddr_in serv_addr, from_addr;
if(argc != 3)
{
printf("Usage : %s <IP> <port> \n", argv[0]);
exit(1);
}
sock = socket(PF_INET, SOCK_DGRAM, 0);
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
while(1)
{
fputs("Inputs message(Q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message, "Q\n"))
break;
sendto(sock, message, strlen(message), 0,
(struct sockaddr*)&serv_addr, sizeof(serv_addr));
addr_sz = sizeof(from_addr);
str_len = recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr*)&from_addr, &addr_sz);
message[str_len] = 0;
printf("message from server : %s \n", message);
}
close(sock);
return 0;
}
void error_handling(char * message)
{
fputs(message, stderr);
fputc(‘\n‘, stderr);
exit(1);
}
UDP
套接字和未连接UDP
套接字使用sendto
分为如下三个阶段:
UDP
套接字注册目标IP
和端口号这种未注册目标地址信息的套接字成为未连接套接字。默认是未连接的套接字,但是如果向同意目标多次传输数据,这样做显然非常抵消,第一个步骤和第三个步骤消耗了大约三分之一的时间。
要创建已连接的套接字只需要针对UDP
套接字调用connect
函数。
sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = ...;
serv_addr.sin_port = ...;
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
针对UDP
套接字调用connect
函数并不意味着要与对方UDP
套接字连接,这只是向UDP
套接字注册目标信息。
注册信息之后再使用 read
和write
读写相关的套接字就可以实现信息交互。
使用shutdown
函数实现半关闭
#include <sys/socket.h>
int shutdown(int sock, int howto); // 成功时返回0,失败时返回-1
~ sock: 需要断开的套接字文件描述符
~ howto: 传递断开的方式 SHUT_RD(关闭度连接-流) SHUT_WR(关闭写连接-流) SHUT_REWD(关闭读写连接-流)
#include <netdb.h>
struct hostent * gethostbyname(const char * hostname); //成功返回 hostent 结构体指针,失败返回 NULL 指针
struct hostent
{
char * h_name; // 官方域名,通常不使用
char ** h_aliases; // 同一主页的多个域名
int h_addrtype; // 地址族信息,若是IPv4, 保存AF_INET
int h_length; // IP 地址长度,4字节或是6字节
char ** h_addr_list; // 同一域名的多个 IP 地址,通过数组保存, h_addr_list 保存数组的地址
}
利用 IP
地址获取域名
#include <netdb.h>
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family); // 同上
~ addr: 含有IP地址信息的 in_addr 结构体指针
~ len: 第一个参数的地址信息的字节数,4或6
~ family: 地址族信息
#include <sys/socket.h>
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen); // 成功返回0, 失败返回-1
~ sock: 要查看的套接字
~ level: 要查看的可选项的协议层
~ optname: 要查看的可选项名
~ optval: 保存查看结果的缓冲地址值
~ optlen: 缓冲地址大小
#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); // 同上
SO_SNDBUF
是输出缓冲区大小,SO_RCVBUF
是输入缓冲区大小。
理解Time-Wait
状态,调整SO_REUSEADDR
参数(置为TRUE
),可将Time-Wait
状态下的套接字端口号重新分配给新的套接字。
理解Negla
算法,通过TCP_NODELAY
参数修改。
标签:
原文地址:http://blog.csdn.net/yapian8/article/details/44242977