document.txt
*套接字通信流程
Winsock库的加载和卸载
要使用Windows Socket API进行编程,首先必须调用WSAStartup()函数初始化Winsock动态库。
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
参数一wVersionRequested:为我们要求初始化的Winsock版本号
参数二lpWSAData:为实际初始化成功的WSA(Windows Socket API)版本信息。
在程序末尾,需调用int WSACleanup(void)函数卸载Winsock动态库。
套接字的创建和释放
首先必须调用socket()函数创建一个套接字描述符
// The socket function creates a socket that is bound to a specific service provider.
SOCKET socket(int af,// [in] Address family specification.
int type,// [in] Type specification for the new socket.
int protocol// [in] Protocol to be used with the socket that is specific to the indicated address family.
);
套接字的释放
当不使用socket()创建的套接字时,应该调用closesocket()函数将它关闭
// The closesocket function closes an existing socket.
int closesocket(
SOCKET s// [in] Descriptor identifying the socket to close.
);
绑定套接字到指定的IP地址和端口
对于传输套接字,在执行收发数据前需要对本地端口进行绑定
// The bind function associates a local address with a socket.
int bind(
SOCKET s, // [in] Descriptor identifying an unbound socket.
const struct sockaddr FAR *name, // [in] Address to assign to the socket from the SOCKADDR structure.
int namelen // [in] Length of the value in the name parameter.
);
bind()函数用在套接字连接建立之前,它的作用是绑定面向连接(connection oriented)的或者面向无连接
(transaction oriented)的套接字。当一个套接字被socket函数创建以后,它存在于指定的地址家族里,
但是它是匿名的。bind()函数通过安排一个本地名称到未命名的socket建立此socket的本地关联。
本地名称包含3个部分:主机地址、协议号(TCP或UDP)和端口号。
TCP服务器设置套接字进入监听状态
// The listen function places a socket a state where it is listening for an incoming connection.
int listen(
SOCKET s,// [in] Descriptor identifying a bound, unconnected socket.
int backlog// [in] Maximum length of the queue of pending connections.
);
服务器为了接受连接,首先使用socket()函数创建一个套接字,然后使用bind()函数将它绑定到一个本地地址(端口),
再用listen()函数为到达的连接指定一个backlog。
backlog参数指定了正在等待连接的最大队列长度。
客户端主动连接
// The connect function establishes a connection to a specified socket.
int connect(
SOCKET s,// [in] Descriptor identifying an unconnected socket.
const struct sockaddr FAR *name,// [in] Name of the socket to which the connection should be established.
int namelen// [in] Length of name.
);
客户端是连接的发起者(initiate),它通过调用connect()函数主动(active)连接服务器。
TCP服务器接受客户连接请求
// The accept function permits an incoming connection attempt on a socket.
SOCKET accept(
SOCKET s,// [in] Descriptor identifying a socket that has been placed in a listening state with the listen function.
struct sockaddr FAR *addr,// [out] receives the address of the connecting entity, as known to the communications layer.
int FAR *addrlen// [out] the length of addr.
);
服务器进入listen状态后,循环调用accept()接受客户的连接。参数一为监听套接字;参数二为远端客户的地址信息;
该函数返回一个套接字句柄,负责后续与该远端客户的会话通信。
服务器调用socket的listen函数进入监听状态后,connect-accept完成的是TCP三次握手过程(three way or three message handshake):
在一个已绑定或已连接的套接字上获取连接名和对方地址信息
获取sockaddr
int getsockname (SOCKET s, struct sockaddr name, int namelen);
getsockname函数获取已绑定(可能是未调用bind的系统自动绑定)的套接口本地协议地址(sockaddr=IP:PORT)。
int getpeername (SOCKET s, struct sockaddr name, int namelen);
I/O通信
收发数据操作接口:send()/recv()。
关闭套接字(TCP连接)
在无连接的UDP中不存在关闭连接问题,我们recvfrom/sendto完毕即可调用closesocket()回收套接字内核资源。
对于面向连接的TCP通信,关闭一个连接需要四次挥手,以关闭双向信道。
// The shutdown function disables sends or receives on a socket.
int shutdown(
SOCKET s,// [in] Descriptor identifying a socket.
int how// [in] Flag that describes what types of operation will no longer be allowed.
);*
server.cpp
#include
#include
#pragma comment(lib,"WSOCK32.LIB") //wsock32.dll
//报告错误
#define ReportWSAError(s) printf("Error #%d in %s.\n", WSAGetLastError(), #s)
int main(int argc, char * argv[])
{
//初始化winsock
WSADATA wsaData;
WORD sockVersion = MAKEWORD(1, 0); //合并两个字节
if (WSAStartup(sockVersion, &wsaData)) //初始化不成功
{
ReportWSAError(WSAStartup());
return 1;
}
//创建一个sock监听所有连接
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET) //数据类型不符
{
ReportWSAError(socket());
WSACleanup(); // 清理套接字
return 1;
}
// 绑定端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY; //任意本地地址
if (bind(listenSocket, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
ReportWSAError(bind());
WSACleanup(); // Terminates use of the wsock32.dll
return 1;
}
// Associates the listenSocket with a well-known address
//等待连接
if (listen(listenSocket, 2) == SOCKET_ERROR)
{
ReportWSAError(bind());
WSACleanup(); // Terminates use of the wsock32.dll
return 1;
}
//保存连接的客户机地址
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
//为每个客户机创建一个套接字
SOCKET serverSocket;
char echo[] = "Message from the server! \r\n";
while (TRUE)
{
serverSocket = accept(listenSocket, (sockaddr*)&remoteAddr, &nAddrLen);
if (serverSocket == SOCKET_ERROR)
{
ReportWSAError(accept());
continue;
}
printf("Accept a connection: %s \r\n", inet_ntoa(remoteAddr.sin_addr));
send(serverSocket, echo, strlen(echo), 0);
closesocket(serverSocket);
}
// 关闭sock
if (closesocket(listenSocket) == SOCKET_ERROR)
{
ReportWSAError(closesocket());
WSACleanup();
return 1;
}
// 卸载库
if (WSACleanup() == SOCKET_ERROR)
{
ReportWSAError(WSACleanup());
return 1;
}
return 0;
}
client.cpp
#include
#include
#pragma comment(lib,"WSOCK32.LIB") //wsock32.dll
#define ReportWSAError(s) printf("Error #%d in %s./n", WSAGetLastError(), #s)
// class WSAInitializer is a part of the Socket class (on win32)
// as a static instance - so whenever an application uses a Socket,
// winsock is initialized
class WSAInitializer // Winsock Initializer
{
public:
WSAInitializer()
{
if (WSAStartup(0x101, &m_wsadata))
{
exit(-1);
}
}
~WSAInitializer()
{
WSACleanup();
}
private:
WSADATA m_wsadata;
}WsaInit;
int main(int argc, char * argv[])
{
//创建客户端winsock tcp连接
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (clientSocket == INVALID_SOCKET) //sock创建失败
{
ReportWSAError(socket()); //返回错误
return 1;
}
// Fill the sockaddr_in structure,
// which describes the other side(server) of the connection to establish.
// This demo connect to localhost.
// Call function bind to associate a specified local address with the socket
// before connect operation in the case of Multi-NIC.
//太长了,不翻译了
sockaddr_in servAddr; //服务器地址
servAddr.sin_family = AF_INET; //使用的协议族
servAddr.sin_port = htons(8888); //转换为网络字节序
servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.120"); //服务器的网络地址
//建立链接
if (connect(clientSocket, (sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ReportWSAError(connect()); //链接未成功报告错误
return 1;
}
//接收服务器数据
char buff[256];
int nRecv = recv(clientSocket, buff, 256, 0);
if(nRecv > 0)
{
buff[nRecv] = '\0'; //填充一个字符串结束标志
printf("Received data:%s\n", buff);
}
if (closesocket(clientSocket) == SOCKET_ERROR)
{
ReportWSAError(closesocket());
return 1;
}
return 0;
}