Linux之Socket编程
1.什么是Socket?
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),socket就提供了这些操作对应的函数接口。
socket可以看成是用户进程与内核网络协议栈的编程接口。
socket不仅可以用于本机的进程间通信,还可以 用于网络上不同主机的进程间通信。
2.IPv4套接口地址结构
IPV4套妾口地址结构通常也弥为“网际套接字地址结构”,它以 “sockaddr_in”命名,定义在头文件<netinet/in.h>中
struct sockaddr_in{ uint8_t sin_len; //整个sockaddr_in结构体的长度 sa_family_t sin_family; //指定该地址家族,在这里必须设为AF_INET in_port_t sin_port; //端口(2字节) struct in_addr sin_addr; //IPv4的地址(4字节) char sin_zero[8]; //暂不使用,一般将其设置为0 (8字节) }
IPv4套接字一般只需关心3个字段
struct sockaddr_in { sa familytsin_family;/* address family:AF_INET */ in_portt sin_port;/* port in network byte order */ struct in_addr sin_addr;/* internet address */ }; /* Internet address.*/ struct in_addr { uint32_t s_addr;/* address in hetwork byte order }
3.通用地址结构
通用地址结构用来指定与套接字关联的地址
struct sockaddr{ uint8_t sin_len; //整个sockaddr结构体的关度 sa_ family_t sin_family; //指定该地址家族 char sa_data[14]; //由sin_family决定它的形式 }
因为不同的协议地址结构形式可能不一样,通用的可以用于任何协议的接口
一般将IPv4的sockaddr_in强行转换为通用的地址结构sockaddr
4.网络字节序
字节序
大端字节序(Big Endian) :最高有效位(MSB:Most Significant Bit)存储于最低内存地址 处,最低有效位(LSB:Lowest Significant Bit;存储于最高内存地坨处。
小端字节序(l.ittle ndian) :最高有效位(MSB:Most Significant Bit)存储于最高内存地址 处,最低有效位(LSB:Lowest Significant Bit>存储于最低内存地垃处。
主机字节序
不同的主有不同的字节序,如:86为小端字节序,Motorola 6800为 大端字节序,ARM字节序是可配置的。
网络字节序
网络字节序规定为大端字节序
为了将字节序统一,就出现了网络字节序,为大端字节序
编写一个程序测试大小端
#include<stdio.h> int main(void) { unsigned int x=0x12345678; unsigned char *p=(unsigned char*)&x; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); return 0; }
Linux下运行结果:
说明是小端模式
5.字节序的转换函数
uint32_t htonl(uint32_t hostlong); //主机字节序转换为网络字节序 uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); ////网络字节序转换为主机字节序 uint16_t ntohs(uint16_t netshort);
说明:在上述的函数中
h代表 host
n代表 network
s代表 short
I代表 long
程序测试网络字节序
#include<stdio.h> #include<arpa/inet.h> int main() { unsigned int x = 0x12345678; unsigned int y=htonl(x); unsigned char *p=(unsigned char*)&y; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); return 0; }
运行结果:
可见,网络字节序会将本地字节序转换为大端模式
6.地址转换函数
#include <netinet/in.h> #include <arpa/inet.h> int inet_ aton(const char *cp,struct in_addr *inp);//将点分十进制转换为网络字节序的结构 in_addr_t inet_addr(const char *cp); //将点分十进制IP地址转换为32位整数 char *inet_ntoa(struct in_addr in); //将网络字节序的结构转换为点分十进制
一般的地址:点分十进制形式,如:192.168.0.100
测试程序:
int main() { unsigned long addr=inet_addr("192.168.0.100"); cout<<ntohl(addr)<<endl; return 0; } //运行结果:3232235620
int main() { unsigned long addr=inet_addr("192.168.0.100"); struct in_addr ipaddr; ipaddr.s_addr=addr; cout<<inet_ntoa(ipaddr)<<endl; return 0; } //运行结果:192.168.0.100
7.套接字类型
常用的三种:
流式套接字(SOCK_STREAM) (TCP协议)
提供面向连接的、可靠的数据传输服务,数据无 差错,无重复的发送,且按发送顺序接收。
数据报式套接字(SOCK_DGRAM) (UDP协议)
提供无连接服务。不提供无错保证,数据可能丢 失或重复,并且接收顺序混乱。
原始套接字(SOCK_RAW)
可以将应用层直接封装成IP层能认识的协议格式
8.TCP客户/服务器模型
9.回射客户/服务器
10.socket的基本函数
socket函数
头文件
<sys/socket.h>
功能:创建一个套接字用于通信
原型
int socket(int domain,int type,int protocol);
参数
domain:指定通信协议族(protocol family)
Name Purpose Man page AF_UNIX, AF_LOCAL Local communication unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK Appletalk ddp(7) AF_PACKET Low level packet interface packet(7)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套 接字SOCK DGRAM,原始套接字SOCK RAW
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. SOCK_RAW Provides raw network protocol access. SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7). SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result. SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.
protocol:协议类型
返回值:成功返回非负整数,它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
bind函数
功能:绑定一个本地地址到套接字
原型
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
参数
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
listen函数
功能:将套接字用于监听进入的连接
原型
int listen (int sockfd,int backlog);
参数
sockfd: socket函数返回的套接字口
backlog: 规定内核为此套接字排队的最大连妾个数口,表示已完成队列和未完成队列的组合,未完成队列表示三次握手还没有成功的条目
返回值:成功返回0,失败返回-1,规定了并发连接的数目
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列
// 调用listen函数后,就成了被动套接字,否则是主动套接字
// 主动套接字:发送连接(connect)
// 被动套接字:接收连接(accept)
accept函数
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);
sockfd:服务器套接字
addr:将返回对等方的套接字地址,相当于把对方的信息填充到结构体中
addrlen:返回对等方的套接字地址长度
返回值:成功返回非负整数,失败返回-1
connect函数
功能:建立一个连接至addr所指定的套接字
原型
int connect(int sockfd;const struct sockaddr*addr;socklen_t addrlen)
参数
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
返回值:成功返回0,失败返回-1
发送/接收函数
服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作,即实现了网咯中不同进程之间的通信.网络I/O操作有下面几组:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。
close()函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
#include <unistd.h> int close(int fd);
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
11.简单的服务器客户端程序
回射服务器代码
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<error.h> #include<string.h> #include<iostream> using namespace std; #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ } while (0); int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("bind"); } if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen=sizeof(peeraddr); int conn; //已连接套接字(主动) if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); char recvbuf[1024]; while (1) { memset(recvbuf, 0, sizeof(recvbuf)); int ret=read(conn,recvbuf,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,ret); } //关闭套接口 close(conn); close(listenfd); return 0; }
回射客户端代码
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); int main() { //socket int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } struct sockaddr_in cliaddr; memset(&cliaddr, 0, sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(2019); cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 if(bind(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr))<0) { ERR_EXIT("bind"); } //指定服务器的地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //客户端不需要绑定和监听 //connect 用本地套接字连接服务器的地址 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect"); char sendbuf[1024]={0}; char recvbuf[1024]={0}; while (fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(sock,sendbuf,strlen(sendbuf)); read(sock,recvbuf,sizeof(recvbuf)); fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } //关闭套接字 close(sock); return 0; }
客户端的套接字sock和服务器的套接字conn构成连接,两个套接字都有自己的地址
conn是在绑定的时候确定的
sock实在连接成功的时候确定的
12.为客户端绑定端口
客户端一般不需要绑定端口,如果不绑定,一般是随机的端口,但是也可以为其绑定固定的端口
//socket int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } //指定客户端的地址结构 struct sockaddr_in cliaddr; memset(&cliaddr, 0, sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(2019); cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 if(bind(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr))<0) { ERR_EXIT("bind"); } //指定服务器的地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //客户端不需要绑定和监听 //connect 用本地套接字连接服务器的地址 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect");
在服务器端接收客户端后,打印出客户端的端口
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
结果是:
13.地址复用REUSEADDR
服务器在重启后,在重新运行时绑定会报地址已经使用,因为现在服务器处于TIME_WAIT状态
这个状态下,不能再绑定客户端,需要等待一段时间。
解决办法:地址复用
服务器端尽可能使用REUSEADDR
在绑定之前尽可能调用setsockopt来设置REUSEADDR套接字选项。
使用REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器。
使用REUSEADDR可以在TIME_WAIT状态还没有消失的时候,就允许重启
//地址复用 int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("bind"); }
14.简单的多进程服务器连接多个客户端
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<error.h> #include<string.h> #include<iostream> using namespace std; ////////////////////////////////////////// #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ } while (0); void do_service(int conn) { char recvbuf[1024]; while (1) { memset(recvbuf, 0, sizeof(recvbuf)); int ret = read(conn, recvbuf, sizeof(recvbuf)); if(ret==0) //客户端关闭了 { printf("client_close!"); break; } else if(ret==-1) { ERR_EXIT("read"); } fputs(recvbuf, stdout); write(conn, recvbuf, ret); } } /////////////////////////////////////////////////// int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("bind"); } if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen=sizeof(peeraddr); int conn; //已连接套接字(主动) pid_t pid; while (1) { if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) { ERR_EXIT("fork"); } if (pid==0) { /* 子进程的处理 */ close(listenfd); //子进程不需要处理监听,子进程处理通信细节 //通信处理封装函数 do_service(conn); //一旦客户端关闭进程返回了,这个子进程就要结束 exit(EXIT_SUCCESS); } else { /*父进程的处理 */ close(conn); //父进程不需要处理连接 } } return 0; }
一个连接对应一个进程来并发处理
服务器有两种套接字,
1.监听套接字:处理三次握手,一旦三次握手创建完成,就将其放在已连接队列中,accept就可以从队列中返回一个连接
2.已连接套接字:accept返回的套接字,主要用来通信,并不能用来接受连接
15.使用多进程实现点对点的聊天程序
服务器
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #include <iostream> #include<signal.h> using namespace std; #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); //信号处理函数 void handler(int sig) { printf("recv a sig=%d\n",sig); exit(EXIT_SUCCESS); } int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { ERR_EXIT("bind"); } if (listen(listenfd, SOMAXCONN) < 0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; //已连接套接字(主动) if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid_t pid; pid=fork(); if(pid==-1) { ERR_EXIT("fork"); } if(pid==0) //子进程,发送数据 { signal(SIGUSR1,handler); //handler是受到信号后的处理函数 char sendbuf[1024]; while (fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(conn,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } exit(EXIT_SUCCESS); } else //父进程,接受数据 { char recvbuf[1024]; while (1) { memset(recvbuf, 0, sizeof(recvbuf)); int ret = read(conn, recvbuf, sizeof(recvbuf)); if(ret==-1) //读取失败 { ERR_EXIT("read"); } if(ret==0) //对方关闭 { printf ("peer close\n"); break; } fputs(recvbuf, stdout); } //父进程退出时,向子进程发送kill信号 kill(pid,SIGUSR1); //父进程得到的pid是子进程的pid,子进程得到的pid为0 exit(EXIT_SUCCESS); } return 0; }
客户端
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #include<signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); //信号处理函数 void handler(int sig) { printf("recv a sig=%d\n", sig); exit(EXIT_SUCCESS); } int main() { //socket int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { ERR_EXIT("socket"); } // struct sockaddr_in cliaddr; // memset(&cliaddr, 0, sizeof(cliaddr)); // cliaddr.sin_family = AF_INET; // cliaddr.sin_port = htons(2019); // cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 // if (bind(sock, (struct sockaddr *)&cliaddr, sizeof(cliaddr)) < 0) // { // ERR_EXIT("bind"); // } //指定服务器的地址结构 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //客户端不需要绑定和监听 //connect 用本地套接字连接服务器的地址 if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); pid_t pid; pid=fork(); if(pid==-1) { ERR_EXIT("fork"); } if (pid==0) //子进程,接受数据 { char recvbuf[1024]; while (1) { memset(recvbuf,0,sizeof(recvbuf)); int ret=read(sock,recvbuf,sizeof(recvbuf)); if(ret==-1) { ERR_EXIT("read"); } else if(ret==0) { printf("peer close\n"); break; } fputs(recvbuf,stdout); } close(sock); kill(getppid(),SIGUSR1); //杀掉父进程 } else //父进程,发送数据 { signal(SIGUSR1,handler); char sendbuf[1024]={0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { write(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } close(sock); } return 0; }
16.socket中TCP的三次握手建立连接详解
我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
17.socket中TCP的四次挥手释放连接详解
上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次挥手释放连接的过程,请看下图:
图示过程如下:
- 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
- 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
- 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
- 接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
18.流协议与粘包
TCP 字节流,无边界,对于对等方来说,不能保证一次读操作返回的是一个消息还是多个消息,存在粘包问题
UDP 报文消息,有边界,能保证对等方,一次读操作返回的是一个消息
粘包产生的原因
1.应用层缓冲区大小超过了套接口发送的缓冲区,消息被分隔
2.TCP传输有最大MSS的限制,可能产生分隔
3.如果传输的大小超过了MTU的限制,会在ip层进行分割
4.其他:流量控制,拥塞控制等
19.readn/writen函数的封装
readn
ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char *)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; }
writen
ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char *)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; }
readn和wirten都是以定长包的形式发送,但是不一定每次都要发送的实际数据报这么长的字节,就增加了网络的负担
改进:可以自定义网络协议的包,定义一个包结构体,存储数据的长度和数据
19.解决粘包问题
本质上是要在应用层维护消息与消息的边界
定长包
包尾加\r\n(ftp)(本来就有,就无法区分)
包头加上包体长度(先接受包头的长度,再根据包头接受包体的长度)
更复杂的应用层协议
20.改进后的回射客户/服务器程序1(使用存储长度和数据的结构体):
服务器
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<error.h> #include<string.h> #include<errno.h> #include<iostream> using namespace std; ////////////////////////////////////////// #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ } while (0); //自定义包结构体 struct packet { int len; //存放数据的实际长度 char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char *)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char *)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } void do_service(int conn) { struct packet recvbuf; int n; //包的长度 while (1) { memset(&recvbuf, 0, sizeof(recvbuf)); int ret = readn(conn, &recvbuf.len, 4); //先接受4个字节 if (ret == -1) { ERR_EXIT("read"); } if (ret <4) //客户端关闭了 { printf("client_close!"); break; } n = htonl(recvbuf.len); ret = readn(conn, recvbuf.buf, n); if (ret == -1) { ERR_EXIT("read"); } if (ret<n) //客户端关闭了 { printf("client_close!"); break; } fputs(recvbuf.buf, stdout); writen(conn, &recvbuf, 4+n); } } /////////////////////////////////////////////////// int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("bind"); } if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen=sizeof(peeraddr); int conn; //已连接套接字(主动) pid_t pid; while (1) { if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid=fork(); if(pid==-1) { ERR_EXIT("fork"); } if (pid==0) { /* 子进程的处理 */ close(listenfd); //子进程不需要处理监听,子进程处理通信细节 //通信处理封装函数 do_service(conn); //一旦客户端关闭进程返回了,这个子进程就要结束 exit(EXIT_SUCCESS); } else { /*父进程的处理 */ close(conn); //父进程不需要处理连接 } } return 0; }
客户端
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #include<errno.h> ////////////////////////////////////////////////////////////// #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); //自定义包结构体 struct packet { int len; //存放数据的实际长度 char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char *)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char *)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ///////////////////////////////////////////////////////////////// int main() { //socket int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } // struct sockaddr_in cliaddr; // memset(&cliaddr, 0, sizeof(cliaddr)); // cliaddr.sin_family = AF_INET; // cliaddr.sin_port = htons(2019); // cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 // if(bind(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr))<0) // { // ERR_EXIT("bind"); // } //指定服务器的地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //客户端不需要绑定和监听 //connect 用本地套接字连接服务器的地址 if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) ERR_EXIT("connect"); struct packet sendbuf; struct packet recvbuf; memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); int n; //包的长度 //输入字符串 while (fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL) { n=strlen(sendbuf.buf); sendbuf.len=htonl(n); //写入套接字,writen发送定长包 writen(sock,&sendbuf,4+n); //读取套接字 int ret = readn(sock, &recvbuf.len, 4); //先接受4个字节,头部长度 if (ret == -1) { ERR_EXIT("read"); } if (ret < 4) //对方关闭了 { printf("client_close!"); break; } n=htonl(recvbuf.len); ret = readn(sock, recvbuf.buf, n); if (ret == -1) { ERR_EXIT("read"); } else if (ret < n) //对等方关闭了 { printf("client_close!"); break; } fputs(recvbuf.buf,stdout); memset(&sendbuf,0,sizeof(sendbuf)); memset(&recvbuf,0,sizeof(recvbuf)); } //关闭套接字 close(sock); return 0; }
发送方:先发送包体长度,再发送数据包体
接收方:先接受长度,在接受对应长度的包体
这样,就进行了消息和消息的边界的区分,解决了粘包问题
21.recv函数
MSG_PEEK可以接受缓冲器的数据,但是并不将数据从缓冲区清除
但read函数在接收过程中将缓冲区数据清除,一次读将一整行完全读走了,实际上是不可考的,因为TCP是流的形式,消息与消息之间是无边际的,不能假定一次读就返回了整个消息,在应用层可以用\n区分消息之间的边界,因为一行一行发送数据,每一行都有一个\n字符。
读取一行带\n的数据的封装函数readline
//有数据就接受,没有数据就阻塞 ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } }
//读取一行最大的字节数,如果在之前遇到\n就返回 //很多消息是在结尾加\r\n,当读到\n就结束(FTP) //readline函数只能用于套接口 ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; //剩余的字节数 while (1) { //接收到bufp的缓冲区中,recv_peek不会清除sockfd中的数据 ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; //接收到的字节数 nread = ret; int i; for (i=0; i<nread; i++) //判断bufp中是否有换行符 { if (bufp[i] == '\n') { //下标为i总共有i+1个字节 ret = readn(sockfd, bufp, i+1); if (ret != i+1) //接收失败 exit(EXIT_FAILURE); return ret; } } if (nread > nleft) //读到的字节数大于剩余的字节数 exit(EXIT_FAILURE); nleft -= nread; //剩余的字节 //读取走nread个字节 ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; //下一次的指针偏移量 } return -1; }
22.改进后的回射客户/服务器程序2(使用readline):
服务器
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #include <errno.h> ////////////////////////////////////////////////////////////// #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); //自定义包结构体 struct packet { int len; //存放数据的实际长度 char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char *)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char *)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } //有数据就接受,没有数据就阻塞 ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } //读取一行最大的字节数,如果在之前遇到\n就返回 //很多消息是在结尾加\r\n,当读到\n就结束(FTP) //readline函数只能用于套接口 ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = (char*)buf; int nleft = maxline; //剩余的字节数 while (1) { //接收到bufp的缓冲区中,recv_peek不会清除recv_peek中的数据 ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; //接收到的字节数 nread = ret; int i; for (i = 0; i < nread; i++) //判断bufp中是否有换行符 { if (bufp[i] == '\n') { //下标为i总共有i+1个字节 ret = readn(sockfd, bufp, i + 1); if (ret != i + 1) //接收失败 exit(EXIT_FAILURE); return ret; } } if (nread > nleft) //读到的字节数大于剩余的字节数 exit(EXIT_FAILURE); nleft -= nread; //剩余的字节 //读取走nread个字节 ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; //下一次的指针偏移量 } return -1; } void do_service(int sock) { char recvbuf[1024]={0}; while (1) { memset(recvbuf,0,sizeof(recvbuf)); int ret=readline(sock,recvbuf,sizeof(recvbuf)); if (ret == -1) { ERR_EXIT("read"); } else if (ret == 0) { printf("peer close\n"); break; } writen(sock,recvbuf,strlen(recvbuf)); } close(sock); } /////////////////////////////////////////////////// int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { ERR_EXIT("bind"); } if (listen(listenfd, SOMAXCONN) < 0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; //已连接套接字(主动) pid_t pid; while (1) { if ((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); pid = fork(); if (pid == -1) { ERR_EXIT("fork"); } if (pid == 0) { /* 子进程的处理 */ close(listenfd); //子进程不需要处理监听,子进程处理通信细节 //通信处理封装函数 do_service(conn); //一旦客户端关闭进程返回了,这个子进程就要结束 exit(EXIT_SUCCESS); } else { /*父进程的处理 */ close(conn); //父进程不需要处理连接 } } return 0; }
客户端
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #include <errno.h> ////////////////////////////////////////////////////////////// #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); //自定义包结构体 struct packet { int len; //存放数据的实际长度 char buf[1024]; }; ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char *)buf; while (nleft > 0) { if ((nread = read(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char *)buf; while (nleft > 0) { if ((nwritten = write(fd, bufp, nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) continue; bufp += nwritten; nleft -= nwritten; } return count; } //有数据就接受,没有数据就阻塞 ssize_t recv_peek(int sockfd, void *buf, size_t len) { while (1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } //读取一行最大的字节数,如果在之前遇到\n就返回 //很多消息是在结尾加\r\n,当读到\n就结束(FTP) //readline函数只能用于套接口 ssize_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = (char*)buf; int nleft = maxline; //剩余的字节数 while (1) { //接收到bufp的缓冲区中,recv_peek不会清除recv_peek中的数据 ret = recv_peek(sockfd, bufp, nleft); if (ret < 0) return ret; else if (ret == 0) return ret; //接收到的字节数 nread = ret; int i; for (i = 0; i < nread; i++) //判断bufp中是否有换行符 { if (bufp[i] == '\n') { //下标为i总共有i+1个字节 ret = readn(sockfd, bufp, i + 1); if (ret != i + 1) //接收失败 exit(EXIT_FAILURE); return ret; } } if (nread > nleft) //读到的字节数大于剩余的字节数 exit(EXIT_FAILURE); nleft -= nread; //剩余的字节 //读取走nread个字节 ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; //下一次的指针偏移量 } return -1; } void do_service(int sock) { char sendbuf[1024]={0}; char recvbuf[1024]={0}; while (fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { writen(sock,sendbuf,strlen(sendbuf)); int ret=readline(sock,recvbuf,sizeof(recvbuf)); if(ret==-1) { ERR_EXIT("readline"); } else if(ret==0) { printf("server close\n"); break; } fputs(recvbuf,stdout); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); } close(sock); } ///////////////////////////////////////////////////////////////// int main() { //socket int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { ERR_EXIT("socket"); } // struct sockaddr_in cliaddr; // memset(&cliaddr, 0, sizeof(cliaddr)); // cliaddr.sin_family = AF_INET; // cliaddr.sin_port = htons(2019); // cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 // if(bind(sock,(struct sockaddr*)&cliaddr,sizeof(cliaddr))<0) // { // ERR_EXIT("bind"); // } //指定服务器的地址结构 struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //客户端不需要绑定和监听 //connect 用本地套接字连接服务器的地址 if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); do_service(sock); close(sock); return 0; }
23.获得已连接的本地的套接口getsockname
原型
#include <sys/socket.h> int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:套接字
add:接收返回结果的地址结构
addrlen:接收地址的长度
返回值:
若无错误发生,getsockname()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
connect... ... struct sockaddr_in localaddr; socklen_t addrlen=sizeof(localaddr); gersockname(sock,(struct sockaddr*)&localaddr,&addrlen) printf("client: ip=%s | port=%d\n",inet_ntoa(loacaladdr.sin_addr),ntohs(localaddr.sin_port)); ...
24.获得已连接的对等方的套接口getpeername
原型:
#include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
只有在已经连接后才能获得对等方的套接口信息
参数:addr还是接收返回结果的地址结构
返回值:成功0,失败-1。
25.获取主机名称gethostname
原型:
#include <unistd.h> int gethostname(char *name, size_t len);
参数:
name:接收返回值的空间
len:空间的大小
返回值:成功0,失败-1。
26.通过主机名获取主机下的所有IP地址gethostbyname
原型:
#include <netdb.h> extern int h_errno; struct hostent *gethostbyname(const char *name);
char host[100]={0} if(gethostname(host,sizeof(host))<0) ERR_EXIT("gethostname"); struct hostent *hp; if(hp=gethostbyname(host)==NULL) ERR_EXIT("gethostname"); int i=0; while(hp->h_addr_list[i]!=NULL) { cout<<inet_ntoa((struct in_addr*)hp->h_addr_list[i]); //先强转为struct in_addr结构,再将其网络地址转换为点分十进制 i++; }
hostent结构体指针
The hostent structure is defined in <netdb.h> as follows: struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses (保存地址列表)*/ } #define h_addr h_addr_list[0] /* for backward compatibility (如果只获取第一个IP,可以用h_addr宏来代替)*/
27.获得本机IP(第一个IP)函数的封装
int getlocalip(char* ip) //ip是用来存储返回的地址的空间 { char host[100]={0}; if(gethostname(host,sizeof(host))<0) return -1; struct hostent *hp; if((hp=gethostbyname(host))==NULL) return -1; strcpy(ip,inet_ntoa(*(struct in_addr*)hp->h_addr_list[0])); //只获得第一条IP return 0; }
如果只获取第一个IP,可以用h_addr宏来代替
... strcpy(ip,inet_ntoa(*(struct in_addr*)hp->h_addr)); //只获得第一条IP