转自
http://xiekeli.blogbus.com/logs/4019775.html
前段时间根据客服的反映,老翁的前置机程序存在不工作的情况,初步表现为GPRS登录失败,我查看了报文(强烈要求老板发奖金,有什么问题我总
是冲锋在前)发现基本出现在网络频繁断开的情况后(网络每隔10分钟被断开一次,socket错误10053,什么原因还不得而知)。忘了说了,前置机是
通过TCP连接到省局的GPRS代理服务器(是由小赖开发的)然后和现场的终端进行通信。前置机程序中是通过delphi的clientsocket进行
连接的。一下子还真不知道是什么原因。对于socket这块我绝对不是专家,知其然,不知其所以然。于是我决定先从清理基本概念开始:
鸟瞰TCP/IP体系结构
首先从TCP/IP体系结构开始(这也是不少公司面试时的必备良题啊),相信下图已经表达得非常清除了。
其次是winsocket与tcp/ip(其实,不止TCP/IP协议族,这里只讨论TCP/IP)
TCP/IP协议核心与应用程序关系图。
最后是常用协议特性:
关于定址
Winsock中,通过SOCKADDR_IN结构来描述IP地址和服务端口:
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
哦,我只关心IP协议,所以sin_family = AF_INET;
关于端口要注意哦,0-1023为固定服务保留的(别打他们的注意了);1024-49151供普通用户的普通用户进程使用;49152-65535是动态和私有端口。
几个特殊地址:
INADDR_ANY:允许服务器应用监听主机上每个网络接口上的客户机活动;
INADDR_BROADCAST用于在一个IP网络中发送广播UDP数据报。
字节排序:
从主机字节顺序---> 网络字节顺序
返回四字节,用于IP地址
u_long htonl(u_long hostlong)
int WSAHtonl(
SOCKET s,
u_long hostlong,
u_long FAR * lpnetlong
);
返回两字节,用于端口号
u_short htons(u_short hostshort);
int WSAHtons(
SOCKET s,
u_short hostshort,
u_short FAR * lpnetshort
);
对应的反向函数:
u_long ntohl(u_long netong)
int WSANtohl(
SOCKETs,
u_long netong,
u_long FAR * lphostlong
);
u_short htons(u_short netshort);
int WSANtons(
SOCKET s,
u_short netshort,
u_short FAR * lphostshort
);
进入winsocket
下面开始整理winsocket 的一些细节:
所有的winsocket应用其实都是调用winsock dll 中的方法,所以通过WSAstartup加载是第一步。否则就会出错:WSANOTINITIALISED(10093)。
下面先来看看面向连接的协议:
从服务器端来看:
1.bind,将套接字和一个已知的地址进行绑定。
这样就创建了一个流套接字,这个步骤最常见的错误是WSAEADDRINUSE (10048) ,表示另外一个进程已经和本地IP和端口进行了绑定,或者那个IP地址和端口号处于TIME_WAIT状态。
2.Listen,将套接字置于监听状态。
int listen(
SOCKET s,
int backlog
)
backlog参数指定了正在等待连接的最大队列长度,如果实际访问的客户端大于该最大长度就会出错:WSAECONNREFUSED (10061)。事实上该backlog本身也是由基层协议提供者决定的。在这个阶段还有一种常见的错误就是WSAEINVAL (10022),即没有绑定就进行监听了。
3.accept和WSAAccept
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR* addrlen,
调用accept可为待决连接队列中的第一个连接请求提供服务。(在服务器端接收连接前,所有的客户端连接请求是放在一个“待决”队列中的。)
accept会返回一个新的套接字描述符,它对应于已经接受的那个客户机连接。对于
该客户机后续的所有操作,都应使用这个新套接字。至于原来那个监听套接字,它仍然用于
接受其他客户机连接,而且仍处于监听模式。
SOCKET WSAAccept(
SOCKET s,
struct sockaddr FAR *addr,
LPINT addrlen,
LPCONDITIONPROC lpfncondition,
DWORD dwCallBackData
)
对于客户端相对要简单得多,主要由以下几步:
1) 用socket或WSASocket创建一个套接字。
2) 解析服务器名(以基层协议为准)。
3) 用connect或WSAConnect初始化一个连接。
在connect过程常发生的错误有:WSAECONNREFUSED (10061)连接的计算机没有监听指定端口的进程;WSAETIMEDOUT (10060)这种情况一般发生在试图连接的计算机不能用时(亦可能因为到主机之间的路由上出现硬件故障或主机目前不在网上)。
连接之后就是数据传输了,就是发送和接收了:
int send(
SOCKET s,
const char FAR * buf,
int len,
int flags)
返回发送的字节数,如果出错常见的错误是:WSAECONNABORTED (10053) 这一错误一般发生在虚拟回路由于超时或协议有错而中断的时候。远程主机上的应用通过执行强行关闭或意外中断操作重新设置虚拟虚路时,或远程主机重新启动时,发生的则是WSAECONNRESET(10054)错误。。最后一个常见错误是WSAETIMEOUT(10060),它发生在连接由于网络故障或远程连接系统异常死机而引起的连接中断时。
int recv(
SOCKET s,
const char FAR * buf,
int len,
int flags)
无连接协议
首先从接收端(类似于有连接方式中的服务端,但不是服务端)看,首先也是通过socket或WSAsocket创建套接字。再通过bind进行绑定。下面跳过Listen和Accept步骤,直接等待接收就可以了。
接收函数:
int recvfrom(
SOCKET s,
char FAR * buf,
int len,
int flags,
struct SockAddr FAR *from,
int FAR * fromlen
)
发送:建立SCOKET后调用sendto或WSASendTo
int sendto(
SOCKET s,
char FAR * buf,
int len,
int flags,
struct SockAddr FAR * to,
int FAR * tolen
)