Socket编程分析Word文档下载推荐.docx
《Socket编程分析Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Socket编程分析Word文档下载推荐.docx(9页珍藏版)》请在冰豆网上搜索。
=0)
{
return;
//代表失败
}
2.释放方法:
WSACleanup();
第二式:
构造SOCKET:
1.服务端:
构造监听SOCKET,流式SOCKET.
SOCKET
Listen_Sock
=socket(AF_INET,SOCK_STREAM,0)
2.客户端:
构造通讯SOCKET,流式SOCKET.
SOCKET
Client_Sock
第三式:
配置监听地址和端口:
SOCKADDR_IN
serverAddr
ZeroMemory((char*)&
serverAddr,sizeof(serverAddr));
serverAddr.sin_family=
AF_INET;
serverAddr.sin_port=htons(1234);
/*本地监听端口:
1234*/
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
/*有IP*/
第四式:
绑定SOCKET:
绑定监听SOCKET.
bind(Listen_Sock,(structsockaddr*)&
serverAddr,sizeof(serverAddr))
第五式:
服务端/客户端连接:
等待客户端接入.
Command_Sock
=accept(Listen_Sock,...)
请求与服务端连接.
intret=connect(Client_Sock,...)
第六式:
收/发数据:
等待客户端接入.charbuf[1024].
接收数据:
recv(Command_Sock,buf,...)
或
发送数据:
send(Command_Sock,buf,...)
请求与服务端连接.charbuf[1024].
send(Client_Sock,buf,...)
recv(Client_Sock,buf,...)
第七式:
关闭SOCKET:
关闭SOCKET.
closesocket(Listen_Sock)
closesocket(Command_Sock)
closesocket(Client_Sock)
备注:
第一步:
socket(AF_INET,SOCK_STREAM,0)解读
structsockaddr_inLewis;
Lewis.sin_family
=AF_INET;
Lewis.sin_port
=htons(80);
Lewis.sin_addr.s_addr=inet_addr("
202.96.134.133"
);
//sin_addr.s_addr也是头文件规定
memset(Lewis.sin_zero,0,sizeof(Lewis.sin_zero));
分析:
我们设置了一个名叫Lewis的套接字地址,它基于TCP/IP协议,因此sin_family的值为AF_INET,这个是雷打不动的,只要使用TCP/IP协议簇,该值就是AF_INET;
htons是端口函数,以后介绍,这就表示设置了端口号为80;
函数原型:
intsocket(intdomain,inttype,intprotocol);
参数说明:
domain:
协议域,又称协议族(family)。
常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。
协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:
指定Socket类型。
常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。
流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。
数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
protocol:
指定协议。
常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:
1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。
当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
第二步:
ntohs,ntohl,htons,htonl的比较和详解
ntohs=nettohostshortint16位
htons=hosttonetshortint16位
ntohl=nettohostlongint32位
htonl=hosttonetlongint32位
网络字节顺序NBO(NetworkByteOrder)
按从高到低的顺序存储,在网络上使用同一的网络字节顺序,可避免兼容性问题;
主机字节顺序HBO(HostByteOrder)
不同的机器HBO不相同,与CPU的设计有关,数据的顺序是由CPU决定的,而与操作系统无关;
如Intelx86结构下,short型数0x1234表示为3412,int型数0x12345678表示为78563412;
如IBMpowerPC结构下,short型数0x1234表示为1234,int型数0x12345678表示为12345678.
由于这个原因,不同体系结构的机器之间不能直接通信,所以要转换成一种约定的顺序,也就是网络字节顺序,其实就是如同powerpc那样的顺序。
在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换
ntohs()
简述:
将一个无符号短整形数从网络字节顺序转换为主机字节顺序。
#include<
winsock.h>
u_shortPASCALFARntohs(u_shortnetshort);
netshort:
一个以网络字节顺序表达的16位数。
注释:
本函数将一个16位数由网络字节顺序转换为主机字节顺序。
返回值:
ntohs()返回一个以主机字节顺序表达的数。
htons()
简述:
将主机的无符号短整形数转换成网络字节顺序。
#include<
u_shortPASCALFARhtons(u_shorthostshort);
hostshort:
主机字节顺序表达的16位数。
注释:
本函数将一个16位数从主机字节顺序转换成网络字节顺序。
返回值:
htons()返回一个网络字节顺序的值。
第三步:
关于地址
INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
一般来说,在各个系统中均定义成为0值。
在Internet地址族中,一个名字包括几个组成部分,对于SOCK_DGRAM和SOCK_STREAM类套接口,名字由三部分组成:
主机地址,协议号(显式设置为UDP和TCP)和用以区分应用的端口号。
如果一个应用并不关心分配给它的地址,则可将Internet地址设置为INADDR_ANY,或将端口号置为0。
如果Internet地址段为INADDR_ANY,则可使用任意网络接口,且在有多种主机环境下可简化编程。
如果端口号置为0,则Windows套接口实现将给应用程序分配一个值在1024到5000之间的唯一的端口。
应用程序可在bind()后用getsockname()来获知所分配的地址,但必需注意的是,getsockname()只有在套接口连接成功后才会填写Internet地址,这是由于在多种主机环境下若干种Internet地址都是有效的。
第四步:
永久循环
那么while
(1)其中1代表一个常量表达式,他永远不会等于0。
所以,循环会一直执行下去。
第五步绑定地址
intbind(intsockfd,conststructsockaddr*addr,socklen_t*addrlen);
bind()把用addr指定的地址赋值给用文件描述符代表的套接字sockfd。
addrlen指定了以addr所指向的地址结构体的字节长度。
一般来说,该操作称为“给套接字命名”。
通常,在一个SOCK_STREAM套接字接收连接之前,必须通过bind()函数用本地地址为套接字命名。
调用bind()函数之后,为socket()函数创建的套接字关联一个相应地址,发送到这个地址的数据可以通过该套接字读取与使用。
bind()函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。
如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind()函数,同时也避免不必要的复杂度。
在一般情况下,对于服务器进程问题需要调用bind()函数,对于客户进程则不需要调用bind()函数。
bind(sockSrv,(SOCKADDR*)&
addrSrv,sizeof(SOCKADDR))
(SOCKADDR*)&
addrSrv将&
addrSrv转换为套接字地址指针
(sockaddr*)&
servAddr
是什么意思
servAddr是个结构体
取servAddr的地址
然后转化为指向sockaddr类型(sockaddr*)的指针
第六步listen
intlisten(SOCKETsock,intbacklog);
//Windows
缓冲区的长度(能存放多少个客户端请求)可以通过listen()函数的backlog参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。
如果将backlog的值设置为
SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
listen()只是让套接字处于监听状态,并没有接收请求。
接收请求需要使用accept()函数。
第二个参数是你监听客户端的最大个数,如连接到主机上的客户端超过其数listen则会返回一个错误代号。
至于队列放在哪可以不关心,可以认为是一个缓冲区。
参数sockfd
被listen函数作用的套接字,sockfd之前由socket函数返回。
在被socket函数返回的套接字fd之时,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。
由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
参数backlog
这个参数涉及到一些网络的细节。
进程处理一个一个连接请求的时候,可能还存在其它的连接请求。
因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。
如果这个情况出现了,服务器进程希望内核如何处理呢?
内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。
这个backlog告诉内核使用这个数值作为上限。
毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。
这个范围是实现相关的。
很难有某种统一,一般这个值会小30以内。
第七步
listen()只是让套接字进入监听状态,并没有真正接收客户端请求,listen()后面的代码会继续执行,直到遇到accept()。
accept()会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
第八步send函数
intsend(SOCKETs,constchar*buf,intlen,intflags);
参数描述:
∙SOCKETs
发送端套接字描述符
∙constchar*buf
应用程序要发送的数据的缓冲区(想要发送的数据)
∙intlen
实际要发送的字节数
∙intflags一般置为0即可
同步Socket的send函数的执行流程如下:
∙调用该函数时,send先比较待发送数据的长度len与套接字s的发送缓冲区的长度(区别于buf),如果len大于s的发送缓冲区的长度,则函数返回SOCKET_ERROR;
∙如果len小于或者等于s发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲区中的数据:
a.如果是在发送,就等待协议将数据发送完毕。
b.如果没有开始发送s的缓冲区中的数据,那么send就比较s的发送缓冲区的剩余空间和len的大小:
如果len大于发送缓冲区剩余空间大小(不足放入剩余发送缓冲区),send就一直等待协议把s发送缓冲区中的数据发送完;
如果len小于发送缓冲区剩余空间大小,就仅仅把buf中的数据copy到发送缓冲区的剩余空间里(send函数返回时并不代表send把s的缓冲区的数据(buf)传到连接的另一端,而是协议传输的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间中)。
如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
如果send在等待协议传送数据时断开网络,那么send函数也返回SOCKET_ERROR。
∙要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间后就返回了,但是此时这些数据并不一定马上被传到连接的另一端。
如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR.(每一个除send之外的Socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。
recv函数:
intrecv(SOCKETs,char*buf,intlen,intflags);
应用程序存放接收的数据的缓冲区
buf的长度
同步Socket的recv函数的执行流程如下:
∙调用recv函数时,recv先等待s的发送缓冲区中的数据被协议发送完毕:
a.如果协议在传送s的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;
b.如果s的发送缓冲区中的数据被协议成功发送完毕或者没有数据时,recv先检查套接字s的接收缓冲区的情况:
如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。
当协议把数据接收完毕,recv函数就把s的接收缓冲区中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。
recv函数仅仅是copy数据,真正接收数据是协议来完成的),recv函数返回其实际copy的字节数。
如果recv在copy时出错,那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
第八步数据传输socket
SOCKETsockConn=accept(sockSrv,(SOCKADDR*)&
addrClient,&
len);
Accept函数返回给的也是socket
sprintf(sendBuf,"
Welcome%stohere!
"
inet_ntoa(addrClient.sin_addr));
//inet_ntoa将sin_addr储存的IP(数值)转换成字符串形式比如:
(127.0.0.1)。
intaccept(ints,structsockaddr*addr,int*addrlen)
服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;
如果失败就返回INVALID_SOCKET。
该函数的第一个参数指定处于监听状态的流套接字;
操作系统利用第二个参数来返回新创建的套接字的地址结构;
操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。
关键备注:
创建新的套接字,取监听的套接字,第二个新创建的地址结构,第三个位长度
intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);
参数
sockfd:
套接字描述符,该套接口在listen()后监听连接。
addr:
(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。
Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:
(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
一些地址的写法:
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_addr.S_un.S_addr=inet_addr("
127.0.0.1"
charsendBuf[50];
inet_ntoa(addrClient.sin_addr))
字符串格式化命令,主要功能是把格式化的数据写入某个字符串中
功能
把格式化的数据写入某个字符串缓冲区。
sprintf头文件
stdio.h
sprintf原型
intsprintf(char*buffer,constchar*format,[argument]…);
sprintf参数列表
buffer:
char型指针,指向将要写入的字符串的缓冲区。
format:
格式化字符串。
[argument]...:
可选参数,可以是任何类型的数据。