一、socket初识
socket可以看成是用户进程与内核网络协议栈的编程接口
socket不仅可以用于本机进程间的通信,还可以用于网络上不同主机的进程间通信。
二、通用地址结构
通用地址结构用来制定与套接字关联的地址
struct socka ddr{
unit8_t sin_len;
sa_family sin_family;
char sa_data[14];
};
sin_len : 整个sockaddr结构体的长度
sin_family:指定该地址家族
sa_data: 由sin_family决定他的形式
三、网络字节序
字节序测试
#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;
}
字节序转换函数
uint32_t htonl(uint32_t hostlong);。
uint16_t htons(uint16_t hostshort);
uint32_t ntonl(uint32_t netlong);
uint16_t ntons(uint16_t netshort);
在上述函数中,h代表host;n代表network;s代表short;l代表long。
四、套接字类型
- 流式套接字(SOCKET_STREAM):提供面向连接的,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
- 数据报式套接字(SOCKET_DGRAM):提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接受顺序混乱。
- 原始套接字(SOCKET_RAW)
五、TCP客户/服务器模型
TCP回射客户/服务器
TCP回射服务器
//回射服务器
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(void)
{
int listenfd;
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);
int on = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if(listen(listenfd,SOMAXCONN) < 0)
ERR_EXIT("listen");
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("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;
}
TCP回射客户端
//回射客户端
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.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;
}else{
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;
}else{
bufp += nwritten;
nleft -= nwritten;
}
}
return count;
}
int main(void)
{
int sock;
if((sock = 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 = inet_addr("127.0.0.1");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("connect");
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf,0,sizeof(sendbuf));
memset(&sendbuf,0,sizeof(recvbuf));
// char sendbuf[1024] = {0};
// char recvbuf[1024] = {0};
int n ;
while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin) != NULL)
{
//通信细节
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);
writen(sock,&sendbuf,4+n);
int ret = readn(sock,&recvbuf.len,4);
if(ret == -1)
{
ERR_EXIT("read");
}
else if (ret < 4)
{
printf("client_close\n");
break;
}else
{
n = ntohl(recvbuf.len);
ret = readn(sock,recvbuf.buf,n);
if(ret == -1)
{
ERR_EXIT("read");
}
else if (ret < n)
{
printf("client_close\n");
break;
}else
{
fputs(recvbuf.buf,stdout);
memset(&sendbuf,0,sizeof(sendbuf));
memset(&sendbuf,0,sizeof(recvbuf));
}
}
}
close(sock);
return 0;
}
六、流协议与粘包
粘包的解决方案(本质上是要在应用层维护消息与消息的边界)
- 定长包
- 包尾加\r\n (ftp)
- 包头加上包体长度
- 更复杂的应用层协议
七、TCP的十一种状态
八、五种I/O模型
-
阻塞I/O
-
非阻塞I/O
-
I/O复用(select和poll)
-
信号驱动I/O
-
异步I/O
九、select管理者
用select来管理I/O,一旦其中一个I/O或者多个I/O检测到我们所感兴趣的事件,select函数返回,返回值未检测到的事件个数。并且返回了那些I/O发生了事件。遍历这些事件进而去处理他。
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
nfds : 读,写,异常集合中的文件描述符的最大值+1
readfds : 读集合
writefds : 写集合
exceptfds : 异常集合
timeout : 超时结构体
十、读、写、异常事件发生条件
- 可读
套接口缓冲区有数据可读
连接的读一半关闭,即接收到了FIN段,读操作将返回0
如果是监听套接口,已完成连接队列不为空时。
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROR选项来获取
- 可写
套接口发送缓冲区有空间容乃数据
连接的写一半关闭。即收到RST段之后,再次调用write操作
套接口上发生了一个错误待处理,错误可以通过getsockopt指定SO_ERROT选项来获取
- 异常
套接口存在带外数据
十一、close和shutdown的区别
- close终止了数据传送的两个方向,既不能往套接字中写入数据,也不能从套接字中读数据。
- shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向
- shutdown
how=1就可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字。而close不能保证,直到套接字引用计数减为0时才发送。也就是说直到所有的进程都关闭了套接字。
十二、套接字I/O超时设置方法
- alarm 闹钟
- 套接字选项 SO_SNDTIMEO SO_RCVTIMEO
- select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装
connect_timeout函数封装
十三、并发与迭代
套接字编程经常使用在客户/服务器编程模型(简称C/S模型)中,C/S模型根据复杂度分为简单的客户/服务器模型和复杂的客户/服务器模型。
C/S简单客户/服务器模型是一对一关系,一个服务器端某一时间段内只对应处理一个客户端的请求,迭代服务器模型属于此模型。
C/S复杂服务器模型是一对多关系,一个服务器端某一时间段内对应处理多个客户端的请求,并发服务器模型属于此模型。
迭代服务器模型和并发服务器模型是socket编程中最常见使用的两种编程模型。
通常来说大多数TCP服务器是并发的,大多数UDP服务器是迭代的
比如我们通常在做聊天室的时候,使用UDP协议来发送消息,但是在用户需要上传下载数据的时候,我们通常在服务器和客户端之间建立一个TCP连接来传送文件。。。
但是如果服务一个客户请求的时间不长,使用迭代服务器没有太大问题,一旦客户请求的时间需要花费很长,不希望整个服务器被单个客户长期占用,而希望同时服务多个客户,就需要选择并发服务器了。
十四、select限制
用select实现的并发服务器,能达到的并发数,受两方面的限制
一个进程能打开的最大文件描述符限制。这可以通过调整内核参数
select中的fd_set集合容量限制(FD_SETSIZE),这需要重新编译内核。
十五、UDP
UDP特点
无连接
基于消息的数据传输服务
不可靠
一般情况下UDP更加高效
UDP客户/服务基本模型
UDP回射客户/服务器
UDP回射服务器
//回射服务器
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void echo_srv(int sock)
{
char recvbuf[1024] = {0};
struct sockaddr_in peeraddr;
socklen_t peerlen;
int n;
while(1)
{
peerlen = sizeof(peeraddr);
memset(recvbuf,0,sizeof(recvbuf));
n = recvfrom(sock,recvbuf,sizeof(recvbuf),0,(struct sockaddr*)&peeraddr,&peerlen);
if (n == 1)
{
if (errno == EINTR)
{
continue;
}
else
{
ERR_EXIT("recvfrom");
}
}
else if(n > 0)
{
fputs(recvbuf,stdout);
sendto(sock,recvbuf,n,0,(struct sockaddr*)&peeraddr,peerlen);
}
}
close(sock);
}
int main(void)
{
int sock;
if((sock = socket(PF_INET,SOCK_DGRAM,0)) < 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);
if (bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
{
ERR_EXIT("bind");
}
echo_srv(sock);
return 0;
}
UDP回射客户端
//回射客户端
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void echo_cli(int sock)
{
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.01");
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
{
sendto(sock,sendbuf,strlen(sendbuf),0,(struct sockaddr*)&servaddr,sizeof(servaddr));
recvfrom(sock,recvbuf,sizeof(recvbuf),0,NULL,NULL);
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0)
ERR_EXIT("socket");
echo_cli(sock);
return 0;
}
UDP注意点
UDP报文可能会丢失重复
UDP报文可能会乱序
UDP缺乏流量控制
UDP协议数据报文截断
recvfrom返回0,不代表连接关闭,因为UDP是无连接的
ICMP异步错误
UDP connnect
UDP外出接口的确定
十六、UNIX域
UNIX域协议特点
UNIX域套接字与TCP套接字相比较,在同一台主机上传输速度前者是后者的两倍
UNIX域套接字可以在同一台主机上各进程之间传输描述符
UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述
UNIX域地址结构
#define UNIX_PATH_MAX 108
struct sockaddr_un{
sa_family_t sun_family; /*AF_UNIX*/
char sun_path[UNIX_PATH_MAX]; /*pathname*/
}
UNIX域套接字编程注意点
bind成功将创建一个文件,权限为0777&umask
sun_path最好用一个绝对路径
UNIX域协议支持流式套接口与报式套接口
UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN;
十七、socketpair
功能:创建一个全双工流管道
原型: intsocketpair(int domain, int type,int protocol,int sv[2]);
参数
domain : 协议家族
type: 套接字类型
protocol:协议类型
sv:返回套接字对
返回值:成功返回0,失败返回-1。
sendmsg/recvmsg两个方法的使用