1. 接口
1.1 转换操作
转换操作主要分为三类:字节序转换操作、IP地址转换操作和主机名转换操作。
1.1.1 字节序转换操作
No. |
函数 |
含义 |
作用 |
1 |
ntohs() |
network to host short |
把unsigned short 类型从网络序转换到主机序 |
2 |
ntohl() |
network to host long |
把unsigned long 类型从网络序转换到主机序 |
No. |
函数 |
含义 |
作用 |
1 |
htons() |
host to network short |
把unsigned short 类型从主机序转换到网络序 |
2 |
htonl() |
host to network long |
把unsigned long 类型从主机序转换到网络序 |
1.1.2 IP地址转换操作
No. |
函数 |
功能 |
特点 |
1 |
int inet_aton(const char *string, struct in_addr*addr) |
点分十进制数串转网络字节序长整型 |
IPv4专用 |
2 |
in_addr_t inet_addr(const char* string) |
点分十进制数串转网络字节序长整型 |
IPv4专用 |
3 |
char* inet_ntoa(struct in_addr addr) |
网络字节序长整型转点分十进制数串 |
IPv4专用 |
4 |
int inet_pton(int af, const char *src, void *dst) |
点分十进制数串转网络字节序长整型 |
IPv4/IPv6通用(推荐) |
5 |
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) |
网络字节序长整型转点分十进制数串 |
IPv4/IPv6通用(推荐) |
结构体
No. |
结构体 |
功能 |
特性 |
1 |
struct sockaddr |
套接字地址结构 |
IPv4/IPv6通用 |
2 |
struct sockaddr_in |
IPv4套接字地址结构 |
IPv4专用 |
3 |
struct in_addr |
IPv4地址结构 |
IPv4专用 |
4 |
in_addr_t |
IPv4地址类型 |
IPv4专用 |
5 |
struct sockaddr_in6 |
IPv6套接字地址结构 |
IPv6专用 |
struct sockaddr {
unsigned short sa_family; // 套接字地址簇类型,为AF_INET
char sa_data[14]; // 套接字地址数据(14位的协议地址)
};
struct sockaddr_in{
short sin_family; // 套接字地址簇类型,为AF_INET
unsigned short sin_port; // 端口号,网络字节序
struct in_addr sin_addr; // IP地址,网络字节序
unsigned char sin_zero[8];// 填充字节
};
sin_zero[8]
用来保证结构体struct sockaddr_in
的大小和结构体struct sockaddr
的大小相等。
struct in_addr {
in_addr_t s_addr;
};
typedef unsigned int in_addr_t;
struct sockaddr_in6{
uint8_t sin6_len; //IPv6 为固定的24 字节长度
sa_family_t sin6_family; //套接字地址簇类型,为AF_INET6
in_port_t sin6_port; //16 位端口号,网络字节序
uint32_t sin6_flowinfo; //32 位流标签
struct in6_addr sin6_addr; //128 位IP地址
}
1.1.2.1 IPv4专用
点分十进制数串转网络字节序长整型
①推荐方式
int inet_aton(const char *string, struct in_addr*addr)
No. |
参数 |
含义 |
1 |
string |
点分十进制IP地址字符串 |
2 |
addr |
网络字节序长整型IP地址 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
非0 |
失败 |
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
if(2 != argc){
printf("usage:%s <ip>\n",argv[0]);
return 1;
}
struct in_addr addr;
int res = inet_aton(argv[1],&addr);
if(0 == res){
perror("inet_aton error");
return 1;
}
printf("res:%d\n%s = 0x%08x",res,argv[1],addr);
return 0;
}
①旧方式
in_addr_t inet_addr(const char* string)
No. |
参数 |
含义 |
1 |
string |
点分十进制IP地址字符串 |
No. |
返回值 |
含义 |
1 |
INADDR_NONE |
失败 |
2 |
非INADDR_NONE
|
网络字节序长整型IP地址 |
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
if(2 != argc){
printf("usage:%s <ip>\n",argv[0]);
return 1;
}
in_addr_t addr = inet_addr(argv[1]);
if(INADDR_NONE == addr){
perror("inet_addr error");
return 1;
}
printf("%s = 0x%08x",argv[1],addr);
return 0;
}
网络字节序长整型转点分十进制数串
char* inet_ntoa(struct in_addr addr)
No. |
参数 |
含义 |
1 |
addr |
网络字节序长整型IP地址 |
No. |
返回值 |
含义 |
1 |
非NULL
|
点分十进制IP地址字符串 |
2 |
NULL |
失败 |
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
if(2 != argc){
printf("usage:%s <num>\n",argv[0]);
return 1;
}
struct in_addr addr;
addr.s_addr = strtol(argv[1],NULL,16);
char* ip = inet_ntoa(addr);
if(NULL == ip){
perror("inet_ntoa error");
return 1;
}
printf("0x%x = %s",addr.s_addr,ip);
return 0;
}
1.1.2.2 IPv4/IPv6通用(推荐)
点分十进制数串转网络字节序长整型
int inet_pton(int af, const char *src, void *dst)
No. |
参数 |
含义 |
1 |
af |
地址族。AF_INET /AF_INET6
|
2 |
src |
点分十进制IP地址字符串 |
3 |
dst |
网络字节序长整型IP地址 |
No. |
返回值 |
含义 |
1 |
<0 |
失败 |
2 |
0 |
af 和src 格式不对 |
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
char ip[INET_ADDRSTRLEN];
if(argc == 1){
scanf("%s",ip);
}else if(2 != argc){
printf("usage:%s <ip>\n",argv[0]);
return 1;
}
struct in_addr addr;
memset(&addr,0,sizeof(addr));
if(1 != inet_pton(AF_INET,(argc == 1?ip:argv[1]),&addr)){
perror("inet_pton err");
return 1;
}
printf("%x\n",addr.s_addr);
}
网络字节序长整型转点分十进制数串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
No. |
参数 |
含义 |
1 |
af |
地址族。AF_INET /AF_INET6
|
2 |
src |
网络字节序长整型IP地址 |
3 |
dst |
点分十进制IP地址字符串 |
4 |
cnt |
缓存区dst 的大小 |
No. |
返回值 |
含义 |
1 |
NULL |
失败 |
2 |
非NULL
|
dst 指针 |
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
if(2 != argc){
printf("usage:%s <dec num>\n",argv[0]);
return 1;
}
struct in_addr addr;
memset(&addr,0,sizeof(addr));
addr.s_addr = strtol(argv[1],NULL,16);
char ip[INET_ADDRSTRLEN];
if(NULL == inet_ntop(AF_INET,&addr,ip,sizeof(ip))){
perror("inet_ntop err");
return 1;
}
printf("%s\n",ip);
}
1.1.3 主机名转换操作
No. |
函数 |
功能 |
1 |
struct hostent *gethostbyname(const char *hostname) |
主机名转地址 |
2 |
struct hostent *gethostbyaddr(const char * addr, int len, int type) |
地址转主机名 |
3 |
struct hostent *gethostbyaddr(const char * addr, int len, int type) |
地址转主机名 |
1.1.3.1 主机名字和地址信息struct hostent
No. |
参数 |
含义 |
1 |
h_name |
主机名字 |
2 |
h_aliases |
以空指针结尾的主机别名队列 |
3 |
h_addrtype |
地址类型。AF_INET /AF_INET6
|
4 |
h_length |
地址长度。在AF_INET 类型地址中为4
|
5 |
h_addr |
第一个IP地址 |
6 |
h_addr_list |
以空指针结尾的IP地址的列表 |
1.1.3.2 主机名转地址
struct hostent *gethostbyname(const char *hostname)
No. |
返回值 |
含义 |
1 |
NULL |
出错 |
2 |
非NULL
|
hostent 结构指针 |
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc,char** argv){
struct hostent* host = gethostbyname(argv[1]);
if(NULL == host){
herror("gethostbyname err");
return 1;
}
printf("hostname:%s\n",host->h_name);
printf("aliases:");
while(*host->h_aliases != NULL){
printf("%s ",*host->h_aliases);
host->h_aliases++;
}
printf("\n");
printf("addrtype:%s\n",host->h_addrtype == AF_INET?"AF_INET":"AF_INET6");
printf("length:%d\n",host->h_length);
printf("addrlist:");
while(*host->h_addr_list != NULL){
printf("%s ",inet_ntoa(*(struct in_addr*)*host->h_addr_list));
host->h_addr_list++;
}
}
1.1.3.3 地址转主机名
struct hostent *gethostbyaddr(const char * addr, int len, int type)
No. |
参数 |
含义 |
1 |
addr |
网络字节顺序地址 |
2 |
len |
地址的长度。在AF_INET 类型地址中为4
|
3 |
type |
地址类型。AF_INET /AF_INET6
|
No. |
返回值 |
含义 |
1 |
NULL |
出错 |
2 |
非NULL
|
hostent 结构指针 |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
int c,af=AF_INET;
while((c = getopt(argc,argv,"6")) != -1){
switch(c){
case '6':
af = AF_INET6;
break;
}
}
if(optind != argc-1){
printf("usage:%s [-6] <ip>\n",argv[0]);
return 1;
}
struct in_addr addr;
struct in6_addr addr6;
void* dst = af == AF_INET ? (void*)&addr : (void*)&addr6;
if(0 >= inet_pton(af,argv[1],dst)){
perror("inet_pton error");
return 1;
}
socklen_t len = AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr) ;
struct hostent * phost = gethostbyaddr(dst,len,af);
if(NULL == phost){
perror("gethostbyname error");
return 1;
}
char ip[20];
switch( phost->h_addrtype ){
case AF_INET:
case AF_INET6:
if(NULL == inet_ntop(phost->h_addrtype,(void*)phost->h_addr,ip,20)){
perror("inet_ntop error");
return 1;
}
printf("host:%s\n0x%x = %s",argv[1],phost->h_addr,ip);
break;
}
}
2. socket
操作
2.1 接口
2.1.1 创建
int socket(int domain, int type, int protocol)
No. |
参数 |
含义 |
1 |
domain |
协议域 |
AF_INET :IPv4;AF_INET6 :IPv6;AF_LOCAL :Unix域 |
2 |
type |
类型 |
SOCK_STREAM :流式套接字;SOCK_DGRAM :数据报套接字;SOCK_RAW :原始套接字 |
3 |
protocol |
协议 |
0 :自动根据type 匹配协议;IPPROTO_TCP /IPPROTO_UDP
|
No. |
返回值 |
含义 |
1 |
-1 |
失败 |
2 |
>0 |
socket描述符 |
2.1.2 关闭
int close(int sockfd)
int shutdown(int sockfd,int howto)
No. |
参数 |
含义 |
1 |
sockfd |
socket套接字 |
2 |
howto |
关闭方式 |
No. |
方式 |
值 |
含义 |
1 |
SHUT_RD |
0 |
关闭连接的读 |
2 |
SHUT_WR |
1 |
关闭连接的写 |
3 |
SHUT_RDWR |
2 |
连接的读和写都关闭 |
2.1.3 属性
设置
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
No. |
参数 |
含义 |
1 |
sockfd |
套接字描述符 |
2 |
level |
选项层次 |
3 |
optname |
选项 |
4 |
optval |
选项值指针 |
5 |
optlen |
optval 缓冲区长度 |
No. |
参数 |
含义 |
1 |
SOL_SOCKET |
通用套接字选项 |
2 |
IPPROTO_TCP |
TCP选项 |
3 |
IPPROTO_IP |
IP选项 |
4 |
IPPROTO_IPV6 |
IPv6选项 |
选项分为SOL_SOCKET
级别和IPPROTO_IP
级别两个级别
No. |
参数 |
含义 |
1 |
SO_REUSEADDR |
让端口释放后立即就可以被再次使用。一个端口释放后会等待两分钟之后才能再被使用。 |
2 |
SO_RCVBUF |
接收确定缓冲区大小 |
3 |
SO_SNDBUF |
发送缓冲区大小 |
4 |
SO_SNDTIMEO |
发送时限 |
5 |
SO_RCVTIMEO |
接收时限 |
6 |
SO_BROADCAST |
广播 |
7 |
SO_DONTLINGER |
关闭端口不进入TIME_WAIT 状态 |
8 |
SO_LINGER |
关闭端口进入TIME_WAIT 状态的属性 |
No. |
参数 |
含义 |
1 |
IP_ADD_MEMBERSHIP |
加入指定的组播组。 |
2 |
IP_DROP_MEMBERSHIP |
离开指定的组播组。 |
3 |
IP_MULTICAST_IF |
指定发送组播数据的IP地址。 |
4 |
IP_MULTICAST_LOOP |
发送组播数据的主机是否作为接收组播数据的组播成员。 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
-1 |
失败 |
获取
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen)
No. |
参数 |
含义 |
1 |
sockfd |
套接字描述符 |
2 |
level |
选项层次 |
3 |
optname |
选项 |
4 |
optval |
选项值指针 |
5 |
optlen |
optval 缓冲区长度 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
-1 |
失败 |
2.1.4 绑定
int bind(int socket, const struct sockaddr* address, socklen_t address_len)
No. |
参数 |
含义 |
1 |
socket |
套接字描述符 |
2 |
address |
地址和端口号 |
3 |
address_len |
address缓冲区的长度 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
SOCKET_ERROR |
失败 |
2.1.5 监听
int listen(int sockfd, int backlog)
No. |
参数 |
含义 |
1 |
sockfd |
监听的socket描述符 |
2 |
backlog |
排队的最大连接个数 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
-1 |
失败 |
2.1.6 连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
No. |
参数 |
含义 |
1 |
sockfd |
客户端的socket描述字 |
2 |
addr |
服务器的socket地址 |
3 |
addrlen |
服务器的socket地址的长度 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
-1 |
失败 |
2.1.7 接受
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
No. |
参数 |
含义 |
1 |
sockfd |
服务器的socket描述符,监听socket描述符 |
2 |
addr |
客户端的socket地址 |
3 |
addrlen |
客户端的socket地址的长度 |
No. |
返回值 |
含义 |
1 |
非-1
|
连接描述符 |
2 |
-1 |
失败 |
如果不需要获取客户端的套接字地址,后两个参数可设置为NULL
。
int connfd = accept(listenfd,NULL,NULL);
如果需要获取,则按照如下方式设置。
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
socklen_t remote_addr_len = sizeof(remote_addr);
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
2.1.8 发送
ssize_t write(int fd, const void *buf, size_t len);
No. |
参数 |
含义 |
1 |
fd |
文件描述符 |
2 |
buf |
写入数据 |
3 |
len |
写入数据的长度 |
No. |
返回值 |
含义 |
1 |
>0 |
实际所写的字节数 |
2 |
<0 |
出错 |
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
No. |
参数 |
含义 |
1 |
sockfd |
sockfd文件描述符 |
2 |
buf |
写入数据 |
3 |
len |
写入数据的长度 |
4 |
flags |
通常为0
|
No. |
返回值 |
含义 |
1 |
>0 |
实际所写的字节数 |
2 |
<0 |
出错 |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
No. |
参数 |
含义 |
1 |
sockfd |
sockfd文件描述符 |
2 |
buf |
写入数据 |
3 |
len |
写入数据的长度 |
4 |
flags |
通常为0
|
5 |
dest_addr |
目标socket地址 |
6 |
addrlen |
目标socket地址长度 |
No. |
返回值 |
含义 |
1 |
>0 |
实际所写的字节数 |
2 |
<0 |
出错 |
2.1.9 接收
ssize_t read(int fd, void *buf, size_t len);
No. |
参数 |
含义 |
1 |
fd |
文件描述符 |
2 |
buf |
读取数据 |
3 |
len |
读取数据的长度 |
No. |
返回值 |
含义 |
1 |
0 |
读到文件的结束 |
2 |
>0 |
实际所读的字节数 |
3 |
<0 |
出错 |
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
No. |
参数 |
含义 |
1 |
fd |
文件描述符 |
2 |
buf |
读取数据 |
3 |
len |
读取数据的长度 |
4 |
flags |
通常为0
|
No. |
返回值 |
含义 |
1 |
0 |
读到文件的结束 |
2 |
>0 |
实际所读的字节数 |
3 |
<0 |
出错 |
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)
No. |
参数 |
含义 |
1 |
fd |
文件描述符 |
2 |
buf |
读取数据 |
3 |
len |
读取数据的长度 |
4 |
flags |
通常为0
|
5 |
dest_addr |
目标socket地址 |
6 |
addrlen |
目标socket地址长度 |
No. |
返回值 |
含义 |
1 |
0 |
读到文件的结束 |
2 |
>0 |
实际所读的字节数 |
3 |
<0 |
出错 |
3. 实践
3.1 特殊地址设置
No. |
特殊地址 |
ipv4 |
ipv6 |
1 |
通配地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_ANY) |
in6_addr.sin6_addr=in6addr_any |
2 |
回环地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK) |
in6_addr.sin6_addr=in6addr_loopback |
3 |
广播地址 |
in_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST) |
无广播 |
3.2 原则
- 客户端/发送端
必须指定连接/发送的IP(广播地址、回环地址或者某个具体地址)。
必须指定连接/发送的port。
- 服务器/接受端
IP指定为通配地址、回环地址或者某个具体地址。
必须指定绑定监听/接受的port。
netstat
查看网络连接状态、socket端口打开状态
No. |
选项 |
作用 |
1 |
-antp |
查看tcp的状态 |
2 |
-anup |
查看udp的状态 |
3.3 TCP基本流程
No. |
C/S |
函数 |
1 |
Server |
socket() 、bind() 、listen() 、accept() 、recv() /read() 、send() /write()
|
2 |
Client |
socket() 、connect() 、send() /write() 、recv() /read()
|
客户端
- 打开套节字
connfd = socket()
- 连接服务器
connect(connfd,...);// 阻塞
- 写入读取数据
write(connfd)/read(connfd)
- 关闭套节字
close(connfd)
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void show_info(int connfd){
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
socklen_t local_addr_len = sizeof(local_addr);
getsockname(connfd,(struct sockaddr*)&local_addr,&local_addr_len);
printf("client local %s:%d\n",inet_ntoa(local_addr.sin_addr),ntohs(local_addr.sin_port));
struct sockaddr_in peer_addr;
bzero(&peer_addr,sizeof(peer_addr));
socklen_t peer_addr_len = sizeof(peer_addr);
getpeername(connfd,(struct sockaddr*)&peer_addr,&peer_addr_len);
printf("clinet peer %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
}
int main(int argc,char* argv[]){
if(3 != argc){
printf("usage:%s <ip> <#port> \n",argv[0]);
return 1;
}
int connfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == connfd){
perror("socket err");
return 1;
}
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
remote_addr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
perror("connect err");
return 1;
}
show_info(connfd);
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
while(fgets(buf,BUFSIZ,stdin) != NULL){
write(connfd,buf,strlen(buf)+1);
printf("client send:%s\n",buf);
bzero(buf,BUFSIZ);
// sendfile(fd,connfd,NULL,);
if(-1 == read(connfd,buf,BUFSIZ)){
perror("read err");
return 1;
}
printf("client recv:%s\n",buf);
}
close(connfd);
}
服务器
- 打开监听套节字
listenfd = socket()
- 设置监听套节字地址
struct sockaddr_in
- 绑定
bind(listenfd)
- 监听
listen(listenfd,backlog)
- 打开连接套节字
connfd = accept(listenfd,...) // 阻塞
- 读写数据
read(connfd)/write(connfd)
recv(connfd)/send(connfd)
recvfrom(connfd)/sendto(connfd);
- 关闭套节字
close(connfd)
close(listenfd)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void show_info(int connfd){
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
socklen_t local_addr_len = sizeof(local_addr);
getsockname(connfd,(struct sockaddr*)&local_addr,&local_addr_len);
printf("server local %s:%d\n",inet_ntoa(local_addr.sin_addr),ntohs(local_addr.sin_port));
struct sockaddr_in peer_addr;
bzero(&peer_addr,sizeof(peer_addr));
socklen_t peer_addr_len = sizeof(peer_addr);
getpeername(connfd,(struct sockaddr*)&peer_addr,&peer_addr_len);
printf("server peer %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
}
int main(int argc,char* argv[]){
if(3 != argc){
printf("usage:%s <ip> <#port>\n",argv[0]);
return 1;
}
int listenfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == listenfd){
perror("listenfd open err");
return 1;
}
printf("socket create OK\n");
int flag = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
struct sockaddr_in local_addr;
bzero(&local_addr,sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = inet_addr(argv[1]);
local_addr.sin_port = htons(atoi(argv[2]));
if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
perror("bind err");
return 1;
}
printf("bind OK\n");
if(-1 == listen(listenfd,10)){
perror("listen err");
return 1;
}
printf("listen OK\n");
struct sockaddr_in remote_addr;
bzero(&remote_addr,sizeof(remote_addr));
socklen_t remote_addr_len = sizeof(remote_addr);
int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
if(-1 == connfd){
perror("accept err");
return 1;
}
show_info(connfd);
printf("accept %s:%d\n",inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port));
char buf[BUFSIZ];
for(;;){
bzero(buf,BUFSIZ);
ssize_t len;
if((len = read(connfd,buf,BUFSIZ-1)) == -1){
perror("read err");
return 1;
}
if(0 == len){
break;
}
printf("server recv:%s\n",buf);
int fd = open(buf,O_RDONLY);
if(-1 == fd){
perror("open file err");
return 1;
}
struct stat file_stat;
fstat(fd,&file_stat);
if(-1 == sendfile(connfd,fd,NULL,file_stat.st_size)){
perror("sendfile err");
return 1;
}
close(fd);
}
close(connfd);
close(listenfd);
}
- 说明
使用下面的代码,是为了避免出现Bind error: Address already in use
int flag = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
启用SO_REUSEADDR
选项后,bind()
函数将允许地址的立即重用。
3.4 UDP
3.4.1 单播
基本流程
① 发送者
- 打开socket
connfd = socket(AF_INET,SOCK_DGRAM,0)
- 设置发送地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET
si.sin_port = htons(端口); // 16位端口,网络序
si.sin_addr.s_addr = inet_addr(IP地址); // IP地址,网络序
- 发送数据
sendto(connfd,buf,buf_size,0,(struct sockaddr *)&si,sizeof(si));
- 关闭socket
close(connfd);
②接收者
- 打开socket
int connfd = socket(AF_INET, SOCK_DGRAM, 0);
- 设置接收地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET; // 套接字地址簇,一般使用AF_INET
si.sin_port = htons(端口); // 16位端口,网络序
si.sin_addr.s_addr = INADDR_ANY; // INADDR_ANY表示接收来自任意IP、任意网卡的发给指定端口的数据
- 端口绑定
int ret = bind(connfd, (struct sockaddr *)&si, sizeof(si));
- 接受数据
recv(connfd,buf,buf_size,0);
- 关闭socket
close(connfd);
示例代码
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv) {
if(4 != argc){
printf("usage:%s <IP> <Port> <MSG>\n",argv[0]);
}
char *ip=argv[1]; //目的主机IP地址
int port = atoi(argv[2]);//目的主机端口号
// 1. 打开socket
int connfd= socket(PF_INET,SOCK_DGRAM,0);
assert(udpfd >= 0);
// 2. 设置发送地址和端口
struct sockaddr_in address; //目的主机地址
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
// 3. 发送数据
sendto(connfd,argv[3],strlen(argv[3])+1,0,(struct sockaddr *)&address,sizeof(address));
// 4. 关闭socket
close(connfd);
return 0;
}
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#define BUFFER_SIZE 1024
int main(int argc,char **argv) {
if(3 != argc) {
printf("usage:%s <IP> <Port> \n",argv[0]);
}
char *ip = argv[1]; //用户自身的IP
int port = atoi(argv[2]); //服务器端口
char buf[BUFFER_SIZE];
// 1. 打开socket
int connfd = socket(PF_INET,SOCK_DGRAM,0);
assert(client_fd >= 0);
// 2. 设置接收地址和端口
struct sockaddr_in address,addr;
socklen_t len = sizeof(addr);
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
// 3. 端口绑定
int ret = bind(connfd,(struct sockaddr *)&address,sizeof(address));
assert(ret != -1);
// 4. 接受数据
printf("recv data\n");
ret = recvfrom(connfd,buf,BUFFER_SIZE-1,0,(struct sockaddr *)&addr,&len);
buf[ret] = '\0';
printf("\n%s\n",buf);
// 5. 关闭socket
close(connfd);
return 0;
}
按照下表设置服务器。
单播接受端
No. |
Recv |
IP |
port |
1 |
unicast_recv |
0.0.0.0 |
8001 |
2 |
unicast_recv |
127.0.0.1 |
8001 |
3 |
unicast_recv |
XXX.XXX.XXX.XXX |
8001 |
单播播发送端
unicast_send 0.0.0.0 8001 HelloWorld
unicast_send 127.0.0.1 HelloWorld
unicast_send XXX.XXX.XXX.XXX 8001 HelloWorld
3.4.2 组播/多播
基本流程
①发送者
与单播发送者一致
②接收者
- 打开套接字
int socket(int domain, int type, int protocol); 返回套接字
- 构建服务器地址结构
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP
serveraddr.sin_port = htons(SERVER_PORT);//端口
- 绑定地址
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- 构建组播属性结构
struct ip_mreqn group;
inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播地址
inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//设置本地地址
group.imr_ifindex=if_nametoindex("ent0");//设置网卡接口
- 设置组播权限和属性
setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));//设置组播权限及选项
- 设置客户端组播地址
struct sockaddr_in cliaddr;
bzero(&cliaddr,sizeof(cliaddr));
cliaddr.sin_family=AF_INET;
inet_pton(AF_INET,GROUP,&cliaddr.sin_addr.s_addr);
cliaddr.sin_port=htons(CLIENT_PORT);
- 发送数据
sendto(sockfd,buf,strlen(buf),0,(structsockaddr*)&cliaddr, sizeof(cliaddr));//往组播地址发送信息,返回数据大小
- 关闭套接字
close(fd);
示例代码
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/route.h>
#include <arpa/inet.h>
void addroute(int connfd,const char* multi_ip,const char* dev){
struct rtentry rt;
memset(&rt,0,sizeof(rt));
struct sockaddr_in* addr = (struct scokaddr_in*)&rt.rt_gateway;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = 0;
addr = (struct scokaddr_in*)&rt.rt_dst;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(multi_ip);
addr = (struct scokaddr_in*)&rt.rt_genmask;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = 0xFFFFFFFF;
rt.rt_flags = RTF_UP|RTF_HOST;
rt.rt_dev = dev;
if(ioctl(connfd,SIOCADDRT,&rt)){
perror("ioctl err");
close(connfd);
}
}
int main(int argc, char *argv[]) {
if(5 != argc){
printf("usage:%s <src_ip> <mulit_ip> <port> <dev>\n",argv[0]);
return 1;
}
int connfd = socket(AF_INET, SOCK_DGRAM, 0);
if(connfd < 0){
perror("socket error");
return 1;
}
// 有些系统可能需要在路由中添加组播地址
// addroute(sock,argv[2],argv[4]);
int reuse = 1;
if(setsockopt(connfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0){
perror("setsockopt SO_REUSEADDR error");
close(connfd);
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[3]));
if(bind(connfd, (struct sockaddr*)&addr, sizeof(addr))){
perror("bind error");
close(connfd);
return 1;
}
struct ip_mreq group;
group.imr_multiaddr.s_addr = inet_addr(argv[2]);
group.imr_interface.s_addr = inet_addr(argv[1]);
if(setsockopt(connfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0){
perror("Adding multicast group error");
close(connfd);
return 1;
}
char buf[BUFSIZ];
if(read(connfd, buf, sizeof(buf)-1) < 0){
perror("read error");
close(connfd);
return 1;
}
printf("recv:%s\n",buf);
close(connfd);
return 0;
}
按照下表设置服务器。
多播接受端
No. |
Recv |
IP |
Multi |
port |
1 |
multicast_recv |
0.0.0.0 |
224.0.1.1 |
8002 |
2 |
multicast_recv |
127.0.0.1 |
224.0.1.1 |
8002 |
3 |
multicast_recv |
XXX.XXX.XXX.XXX |
224.0.1.1 |
8002 |
单播播发送端
unicast_send 224.0.1.1 8002 HelloWorld
3.4.3 广播
基本流程
①发送者
- 打开socket
cfd = socket(AF_INET,SOCK_DGRAM,0) :
- 打开广播
setsockopt(cfd,SOL_SOCKET,SO_BROADCAST,&n,sizeof(n));
No. |
参数 |
含义 |
1 |
n |
0 表示关闭属性,非0 表示打开属性 |
- 设置发送地址和端口
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(端口);
si.sin_addr.s_addr = inet_addr("255.255.255.255");
- 发送数据
sendto(cfd,buffer,buffer_size,0,(struct sockaddr *)&si,sizeof(si));
- 关闭socket
close(cfd);
②接收者
与单播发送者一致
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc,char* argv[]) {
if(4 != argc){
printf("usage:%s <broadcast_ip> <port> <message>\n",argv[0]);
return 1;
}
int connfd= socket(AF_INET,SOCK_DGRAM, 0);
if (-1 == connfd) {
perror("socket error");
return 1;
}
int opt = 1;
if(setsockopt(connfd,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt))==-1) {
perror("setsockopt fail.");
close(connfd);
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
if (-1 == sendto(connfd, argv[3], strlen(argv[3])+1, 0, (struct sockaddr *)&addr, sizeof(addr))) {
perror("sendto error");
close(connfd);
return 1;
}
printf("send ok!\n");
close(connfd);
return 0;
}
按照下表设置服务器。
单播接受端
No. |
Recv |
IP |
port |
1 |
unicast_recv |
0.0.0.0 |
8003 |
2 |
unicast_recv |
127.0.0.1 |
8003 |
3 |
unicast_recv |
XXX.XXX.XXX.XXX |
8003 |
多播接受端
No. |
Recv |
IP |
Multi |
port |
1 |
multicast_recv |
0.0.0.0 |
224.0.1.1 |
8003 |
2 |
multicast_recv |
127.0.0.1 |
224.0.1.1 |
8003 |
3 |
multicast_recv |
XXX.XXX.XXX.XXX |
224.0.1.1 |
8003 |
广播发送端
boardcast_send XXX.XXX.XXX.255 8003 HelloWorld