04socket应用编程专题讲座v100上王保明.docx
- 文档编号:30297876
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:25
- 大小:379.97KB
04socket应用编程专题讲座v100上王保明.docx
《04socket应用编程专题讲座v100上王保明.docx》由会员分享,可在线阅读,更多相关《04socket应用编程专题讲座v100上王保明.docx(25页珍藏版)》请在冰豆网上搜索。
04socket应用编程专题讲座v100上王保明
linuxSocket-应用编程-专题讲座
writtenby王保明
Socket编程基本实践
1SocketApi基本概念
什么是socket?
❑socket可以看成是用户进程与内核网络协议栈的编程接口。
❑socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
tcp/ip通讯模型
IPv4套接口地址结构
❑IPv4套接口地址结构通常也称为“网际套接字地址结构”,它以“sockaddr_in”命名,定义在头文件
structsockaddr_in{
uint8_tsin_len;4
sa_family_tsin_family;4
in_port_tsin_port;2
structin_addrsin_addr;4
charsin_zero[8];8
};
❑sin_len:
整个sockaddr_in结构体的长度,在4.3BSD-Reno版本之前的第一个成员是sin_family.
❑sin_family:
指定该地址家族,在这里必须设为AF_INET
❑sin_port:
端口
❑sin_addr:
IPv4的地址;
❑sin_zero:
暂不使用,一般将其设置为0
通用地址结构
❑通用地址结构用来指定与套接字关联的地址。
structsockaddr{
uint8_tsin_len;
sa_family_tsin_family;
charsa_data[14];//14
};
❑sin_len:
整个sockaddr结构体的长度
❑sin_family:
指定该地址家族
❑sa_data:
由sin_family决定它的形式。
网络字节序
❑字节序
大端字节序(BigEndian)
最高有效位(MSB:
MostSignificantBit)存储于最低内存地址处,最低有效位(LSB:
LowestSignificantBit)存储于最高内存地址处。
❑小端字节序(LittleEndian)
最高有效位(MSB:
MostSignificantBit)存储于最高内存地址处,最低有效位(LSB:
LowestSignificantBit)存储于最低内存地址处。
❑主机字节序
不同的主机有不同的字节序,如x86为小端字节序,Motorola6800为大端字节序,ARM字节序是可配置的。
❑网络字节序
网络字节序规定为大端字节序
字节序转换函数
❑uint32_thtonl(uint32_thostlong);
❑uint16_thtons(uint16_thostshort);
❑uint32_tntohl(uint32_tnetlong);
❑uint16_tntohs(uint16_tnetshort);
❑说明:
在上述的函数中,h代表host;n代表networks代表short;l代表long
地址转换函数
❑#include
❑#include
❑intinet_aton(constchar*cp,structin_addr*inp);
❑in_addr_tinet_addr(constchar*cp);
❑char*inet_ntoa(structin_addrin);
套接字类型
❑流式套接字(SOCK_STREAM)
提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
❑数据报式套接字(SOCK_DGRAM)
提供无连接服务。
不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
❑原始套接字(SOCK_RAW)
2SocketApi基本编程模型
TCP客户/服务器模型
简单服务器模型
3SocketApi基本实践
SocketAPI基本用法
socket函数
❑包含头文件
❑功能:
创建一个套接字用于通信
❑原型
❑intsocket(intdomain,inttype,intprotocol);
❑参数
❑domain:
指定通信协议族(protocolfamily)
❑type:
指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
❑protocol:
协议类型
❑返回值:
成功返回非负整数,它与文件描述符类似,我们把它称为套接口描述字,简称套接字。
失败返回-1
bind函数
❑包含头文件
❑功能:
绑定一个本地地址到套接字
❑原型
❑intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);
❑参数
❑sockfd:
socket函数返回的套接字
❑addr:
要绑定的地址
❑addrlen:
地址长度
❑返回值:
成功返回0,失败返回-1
listen函数
❑一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
❑对于给定的监听套接口,内核要维护两个队列:
❑1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
❑2、已完成连接的队列
accept函数
❑包含头文件
❑功能:
从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
❑原型
❑intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);
❑参数
❑sockfd:
服务器套接字
❑addr:
将返回对等方的套接字地址
❑addrlen:
返回对等方的套接字地址长度
❑返回值:
成功返回非负整数,失败返回-1
connect函数
❑包含头文件
❑功能:
建立一个连接至addr所指定的套接字
❑原型
❑intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);
❑参数
❑sockfd:
未连接套接字
❑addr:
要连接的套接字地址
❑addrlen:
第二个参数addr长度
❑返回值:
成功返回0,失败返回-1
SocketAPI中的地址复用
SO_REUSEADDR
❑服务器端尽可能使用SO_REUSEADDR
❑在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。
使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器
intoptval=1;
if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval))<0)
{
perror("setsockoptbind\n");
exit(0);
}
Socket服务支持多并发(多客户端连接)
分析最基本socket服务器客户端模型能否支持多客户端连接
点对点聊天程序设计与实现
点对点聊天程序,功能说明
点对点聊天程序,设计思想
注意:
客户端退出、服务器端退出,对各自的影响;分析父子进程生命周期关系。
注意:
体会长链接和短连接。
。
。
。
客户端实现思路:
voidmyhanldle(intsig)
{
printf("resvsig:
%d客户端父进程也退出\n",sig);
exit(0);
}
intmain(void)
{
intsock;
sock=socket(PF_INET,SOCK_STREAM,0);
//listenfd=socket(AF_INET,IPPROTO_TCP,0);
if(sock<0)
{
perror("socket:
");
exit(0);
}
structsockaddr_inservaddr;
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(8089);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//绑定服务器任意一个i
//inet_aton("127.0.0.1",&servaddr.sin_addr);*/
if(connect(sock,(structsockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("connect:
");
exit(0);
}
pid_tpid;
pid=fork();
if(pid==-1)
{
perror("forkerr:
");
close(sock);
exit(0);
}
if(pid==0)
{
charrecvbuf[1024]={0};
while
(1)
{
memset(recvbuf,0,sizeof(recvbuf));
intret=read(sock,recvbuf,sizeof(recvbuf));
if(ret==-1)
{
perror("readerr\n");
break;
}
elseif(ret==0)
{
printf("peerclose\n");
break;
}
fputs(recvbuf,stdout);
}
close(sock);
//客户端检测到对方退出以后,发信号给父进程退出
kill(getppid(),SIGUSR1);
printf("客户端子进程退出\n");
exit(0);
}
else
{
charsendbuf[1024]={0};
signal(SIGUSR1,myhanldle);
printf("客户端父进程要求输入数据:
\n");
while(fgets(sendbuf,sizeof(sendbuf),stdin)!
=NULL)
{
printf("客户端获取数据长度:
%d\n",strlen(sendbuf));
write(sock,sendbuf,strlen(sendbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
printf("客户端父进程退出\n");
printf("parentclose\n");
//exit(EXIT_SUCCESS);
}
//close(sock);
return0;
}
服务器端实现思路:
voidmysighandler_t(intsig)
{
printf("childresvsig:
%d,childquit\n",sig);
exit(0);
}
intmain(void)
{
intlistenfd;
listenfd=socket(PF_INET,SOCK_STREAM,0);
//listenfd=socket(AF_INET,IPPROTO_TCP,0);
if(listenfd<0)
{
printf("funcsocket()err\n");
exit(0);
}
structsockaddr_inservaddr;
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(8089);
//servaddr.sin_addr.s_addr=inet_addr(INADDR_ANY);绑定服务器任意一个ip
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//绑定服务器任意一个ip
//inet_aton("127.0.0.1",&servaddr.sin_addr);*/
intoptval=1;
//每个级别SOL_SOCKET下,也有很多选项不同的选择会有不同的结构
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval))<0)
{
perror("setsockopt");
exit(0);
}
if(bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr))<0)
{
perror("bind");
exit(0);
}
if(listen(listenfd,SOMAXCONN)<0)
{
perror("listen");
exit(0);
}
intconn;
structsockaddr_inpeeraddr;
socklen_taddrlen;
memset(&peeraddr,0,sizeof(structsockaddr_in));
addrlen=sizeof(structsockaddr_in);
conn=accept(listenfd,(structsockaddr*)&peeraddr,&addrlen);
if(conn<0)
{
perror("accept");
exit(0);
}
printf("perradd:
%speerport:
%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
intpid=fork();
if(pid==-1)
{
perror("forkerr:
");//创建子进程失败
close(conn);//关闭conn套接字
close(listenfd);//关闭监听套接字
exit(0);
}
close(listenfd);//关闭监听套接字
if(pid==0)
{
signal(SIGUSR1,mysighandler_t);
charsendbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!
=NULL)
{
write(conn,sendbuf,strlen(sendbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
printf("childclose\n");
exit(EXIT_SUCCESS);
}
elseif(pid>0)
{
charrecbuf[1024];
while
(1)
{
memset(recbuf,0,sizeof(recbuf));
//如果对方退出,捕捉
intret=read(conn,recbuf,sizeof(recbuf));
if(ret==0)
{
printf("ret==0peerclose退出\n");
break;//去掉做实验
}
elseif(ret<0)
{
perror("ret<0");
break;
}
fputs(recbuf,stdout);
}
kill(pid,SIGUSR1);
printf("parentquit\n");
close(conn);
exit(0);
}
return0;
}
4SocketApi编程进价
1流协议与粘包
流协议与粘包
c
粘包产生的原因
说明
tcp字节流无边界
udp消息、数据报有边界
对等方,一次读操作,不能保证完全把消息读完。
对方接受数据包的个数是不确定的。
产生粘包问题的原因
1、SQ_SNDBUF套接字本身有缓冲区(发送缓冲区、接收缓冲区)
2、tcp传送的网络数据最大值MSS大小限制
3、链路层也有MTU(最大传送单元)大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。
(可以简单的认为MTU是MSS加包头数据。
)
4、tcp的流量控制和拥塞控制,也可能导致粘包
5、tcp延迟发送机制等等
结论:
tcp/ip协议,在传输层没有处理粘包问题。
粘包解决方案
❑本质上是要在应用层维护消息与消息的边界
❑定长包
❑包尾加\r\n(ftp)
❑包头加上包体长度
❑更复杂的应用层协议
2包头加上包体长度编程实践
。
编程实践
❑readn
❑written
❑发送数据:
前4个字节代表报文长度(网络字节序)+报文内容
❑接受数据:
先读4个字节,然后根据报文长度,再度内容。
//1一次全部读走//2次读完数据//出错分析//对方已关闭
//思想:
tcpip是流协议,不能保证1次读操作,能全部把报文读走,所以要循环读指定长度的数据。
//按照count大小读数据,
//若读取的长度ssize_t //@ssize_t: 返回读的长度若ssize_t //@buf: 接受数据内存首地址 //@count: 接受数据长度 ssize_treadn(intfd,void*buf,size_tcount) { size_tnleft=count; ssize_tnread; char*bufp=(char*)buf; while(nleft>0) { if((nread=read(fd,bufp,nleft))<0) { if(errno==EINTR) continue; return-1; } elseif(nread==0)//若对方已关闭 returncount-nleft; bufp+=nread; nleft-=nread; } returncount; } //1一次全部读走//2次读完数据//出错分析//对方已关闭 //思想: tcpip是流协议,不能1次把指定长度数据,全部写完 //按照count大小写数据 //若读取的长度ssize_t //@ssize_t: 返回写的长度-1失败 //@buf: 待写数据首地址 //@count: 待写长度 ssize_twriten(intfd,constvoid*buf,size_tcount) { size_tnleft=count; ssize_tnwritten; char*bufp=(char*)buf; while(nleft>0) { if((nwritten=write(fd,bufp,nleft))<0) { if(errno==EINTR) continue; return-1; } elseif(nwritten==0) continue; bufp+=nwritten; nleft-=nwritten; } returncount; } 3包尾加上\n编程实践 \n作为协议的边界 ❑ssize_trecv(ints,void*buf,size_tlen,intflags); ❑与read相比,只能用于套接字文件描述符; ❑多了一个flags MSG_OOB Thisflagrequestsreceiptofout-of-banddatathatwouldnotbereceivedinthenormaldatastream.Someprotocolsplaceexpediteddataattheheadofthenormaldataqueue,andthusthisflagcannotbeusedwithsuchprotocols. 带外数据紧急指针 MSG_PEEK Thisflagcausesthereceiveoperationtoreturndatafromthebeginningofthereceivequeuewithoutremovingthatdatafromthequeue.Thus,asubsequentreceivecallwillreturnthesamedata. 可以读数据,不从缓存区中读走,利用此特点可以方便的实现按行读取数据。 一个一个字符的读,方法不好;多次调用系统调用read方法 recv函数会将套接字缓冲区中的内容读出,但不清空,与read函数的区别在此。 此函数有一个flag标志位,设为MSG_PEEK。 send函数会将缓冲区中的内容写入到套接字,也不清空,与write函数的区别在此。 用这两个函数可以先接收或发送缓冲区中的内容,然后再用readn(此时缓冲区中的内容依然存在)与write函数去继续判断换行符/n,对缓冲区内容实现换行输出。 参考例题: 4域名服务相关函数 getsockname&getpeername 获取本地的地址(注意是已连接套接字) NAME getsockname-getsocketname
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 04 socket 应用 编程 专题讲座 v100 上王保明
![提示](https://static.bdocx.com/images/bang_tan.gif)