注:本系列文章适合初学网络编程的读者
网络程序的实现可以有很多方式,Windows Socket就是其中一种比较简单的方法。socket是连接应用程序与网络驱动程序的桥梁,socket在应用程序中创建,通过绑定操作与驱动程序建立关系。此后,应用程序送给socket的数据,由socket交给驱动程序向网络上发送出去。计算机从网络上收到与该socket绑定的IP地址和端口号相关的数据后,由驱动程序交给socket,应用程序便可从该socket中提取接收到的数据。
在TCP/IP网络应用中,通信的两个进程间相互作用的主要是(client/server)模式,即客户向服务器提出请求,服务器接收到请求后,提供相应的服务。
下面通过一个简单的实例来讲述基于TCP的socket编程的通信流程。其中服务器端程序实现代码TCPSrv.cpp如下:

Server
1
//#include <windows.h>
2
#include <Winsock2.h>
3
#include <stdio.h>
4
5
void main()
6

{
7
//加载套接字库
8
WORD wVersionRequested;
9
WSADATA wsaData;
10
int err;
11
12
wVersionRequested = MAKEWORD(1, 1);
13
err = WSAStartup(wVersionRequested, &wsaData);
14
if( err != 0 )
15
{
16
return;
17
}
18
if( LOBYTE(wsaData.wVersion) != 1 ||
19
HIBYTE(wsaData.wVersion) != 1)
20
{
21
WSACleanup();
22
return;
23
}
24
25
//创建用于监听的套接字
26
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
27
SOCKADDR_IN addSrv;
28
addSrv.sin_addr.S_un.S_addr = htonl( INADDR_ANY );
29
addSrv.sin_family = AF_INET;
30
addSrv.sin_port = htons(6000);
31
32
//绑定套接字
33
bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
34
//将套接字设为监听模式,准备接收客户请求
35
listen(sockSrv, 5);
36
37
SOCKADDR_IN addClient;
38
int len = sizeof(SOCKADDR);
39
40
while(1)
41
{
42
//等待客户请求
43
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addClient, &len);
44
char sendBuf[1000];
45
sprintf(sendBuf, "Welcome %s to http://www.cnblogs.com/lantionzy", inet_ntoa(addClient.sin_addr));
46
//发送数据
47
send(sockConn, sendBuf, strlen(sendBuf)+1, 0);
48
49
char receiveBuf[1000];
50
//接收数据
51
recv(sockConn, receiveBuf, 1000, 0);
52
//打印接收的数据
53
printf("%s\n", receiveBuf);
54
//关闭套接字
55
closesocket(sockConn);
56
}
57
}
在这段代码中,首先定义了一个WORD类型的变量:wVersionRequested,用来保存WinSocl库的版本号,接着调用MAKEWORD宏创建一个包含了请求版本号的WORD值,之后调用WSAStartup函数加载套接字库,如果其返回值不等于0 ,则程序退出。接下来判断wsaData.wVersion的低字节和高字节是否都等于1,如果不是我们请求的版本,那么调用WSACleanup函数终止对Winsock库的使用并返回。
加载套接字库后,就可以按照一定流程来编写实现代码了:
1、创建套接字(socket)
利用socket函数创建套接字,对于它来说,第一个参数只能是AF_INET或(PF_INET);本例是基于TCP协议的网络程序,需要创建的是流式套接字,因此将socket函数第二个参数设置为SOCK_STREAM;将其第三个参数指定为0。这样该函数将根据地址格式和套接字类别,自动选择一个合适的协议。
2、将套接字绑定到一个本地地址和端口上(bind)
在SOCKADDR_IN结构体中,除了sa_family成员外,其他成员都是按网络字节顺序表示的。因此使用htonl函数将INADDR_ANY值转换为网络字节顺序。调用bind函把套接字sockSrv绑定到本地地址和指定端口上。其第一个参数为要绑定的套接字,第二个需要一个指针,可以用取地址符来实现,并且addrSrv变量是SOCKADDR_IN结构体类型,而这里需要的是SOCKADDR*类型,所以要进行强制转换。第三个参数是指定地址结构的大小,可以利用sizeof操作符来获取。
3、将套接字设为监听模式(listen),准备接收客户请求。其中listen函数第二个参数是指等待连接队列的最大长度。
4、等待客户请求的到来;当请求到来后,接受连接请求,返回一个新的对应于此连接的套接字(accept)。
接下来,需要调用accept函数等待并接受客户的连接请求。因为作为服务器端,它需要不断的等待客户端的连接请求的到来,所以设计成一个死循环。当客户端有请求时,该函数接受请求建立连接,同时返回一个相对于当前这个新连接的一个套接字描述符,保存于sockConn变量中,然后利用这个套接字就可以与客户端进行通信了,而我们先前的套接字仍继续监听客户端的连接请求。
5、用返回的套接字和客户端进行通信(send/recv)
可以调用send函数向客户端发送数据,注意这里使用的套接字是已建立连接的那个套接字:sockConn,而不是用于监听的那个套接字:addrSrv。使用recv函数从客户端接收数据。
6、返回等待另一个客户请求
7、关闭套接字
上面实现的服务器端的程序,下面是客户端程序实现代码TCPClient.cpp:

Client
1
//#include <windows.h>
2
#include <Winsock2.h>
3
#include <stdio.h>
4
5
int main()
6

{
7
WORD wVersionRequested;
8
WSADATA wsaData;
9
int err;
10
11
wVersionRequested = MAKEWORD(1, 1);
12
err = WSAStartup(wVersionRequested, &wsaData);
13
if( err != 0 )
14
{
15
return -1;
16
}
17
if( LOBYTE(wsaData.wVersion) != 1 ||
18
HIBYTE(wsaData.wVersion) != 1)
19
{
20
WSACleanup();
21
return -1;
22
}
23
24
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
25
SOCKADDR_IN addSrv;
26
addSrv.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" );
27
addSrv.sin_family = AF_INET;
28
addSrv.sin_port = htons(6000);
29
30
connect(sockClient, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
31
32
char receiveBuf[1000];
33
recv(sockClient, receiveBuf, 1000, 0);
34
printf("%s\n", receiveBuf);
35
36
send(sockClient, "This is lantionzy", strlen("This is lantionzy")+1, 0);
37
closesocket(sockClient);
38
39
WSACleanup();
40
}
对于客户端来说,它不需要邦定,可以直接连接服务器端。
首先运行服务器端程序,然后再运行客户端程序,可以看到客户端收到了服务器端返回的信息:Welcome 127.0.0.1 to http://www.cppblog.com/lantionzy,而服务器端收到了客户端发送的信息:This is lantionzy。
剖析网络编程(2)-- 基于UDP的的网络应用程序 剖析网络编程(3)-- 基于TCP/UDP网络编程应注意的几个地方