通讯基本流程图如下所示:
Server端代码(ServerDemo.cpp):
1 #include <WinSock2.h>
2 #include <Windows.h>
3 #include <iostream>
4 #include <string>
5 #include <sstream>
6
7 using namespace std;
8
9 #pragma comment(lib, "WS2_32.lib")
10
11 int main()
12 {
13 /*
14 WSAstartup 必须是应用程序首先调用的 Winsock 函数。它允许应用程序指定所需的
15 Windows Sockets API 的版本,获取特定 Winsock 实现的详细信息。仅当这个函数成功执行之
16 后,应用程序才能调用其他 Winsock API
17 每一个对WSAStartup的调用必须对应一个对WSACleanup的调用, 这个函数释放Winsock库。
18 */
19 WORD wVersion = MAKEWORD(2, 2);
20 WSADATA WSAData;
21 ::WSAStartup(wVersion, &WSAData);
22
23 stringstream os;
24 cout << "初始化套接字...." << endl;
25 SOCKET s;
26 s = ::socket(AF_INET, SOCK_STREAM, 0);
27 if(s == INVALID_SOCKET)
28 {
29 cout << "socket fail!" << endl;
30 goto __end;
31 }
32 sockaddr_in addr_in;
33 addr_in.sin_family = AF_INET; //设置地址家族
34 addr_in.sin_addr.S_un.S_addr = INADDR_ANY;
35 addr_in.sin_port = htons(8080); // 转化端口号8080到网络字节顺序,并安排它到正确的成员
36
37 // 绑定这个套节字到一个本地地址
38 cout << "绑定端口8080...." << endl;
39 if(::bind(s, (LPSOCKADDR)&addr_in, sizeof(addr_in)) == SOCKET_ERROR)
40 {
41 cout << "bind error!" << endl;
42 goto __end;
43 }
44
45 // listen 函数置套节字进入监听状态
46 os << "开始监听...." << inet_ntoa(addr_in.sin_addr) << endl; //inet_ntoa() 将32 位的二进制数转化为字符串
47 cout << os.str() << endl;
48 if(::listen(s, 2) == SOCKET_ERROR)
49 {
50 cout << "listen error!" << endl;
51 goto __end;
52 }
53
54 SOCKET s_client;
55 sockaddr_in addr_client;
56 char szServerMsg[256] = "Hello client, this is server!";
57 int nMsgLen = sizeof(szServerMsg);
58 while(true)
59 {
60 // accept 函数用于接受到来的连接。
61 if ((s_client = ::accept(s, (LPSOCKADDR)&addr_client, &nMsgLen)) == SOCKET_ERROR)
62 {
63 cout << "accept error!" << endl;
64 goto __end;
65 }
66 os.str("");
67 os.clear();
68 os << "接收到来自" << inet_ntoa(addr_client.sin_addr) << "的连接!";
69 cout << os.str() << endl;
70 // send 函数在一个连接的套节字上发送缓冲区内的数据,返回发送数据的实际字节数。flag参数通常设置为0
71 ::send(s_client, szServerMsg, 256, 0);
72 ::closesocket(s_client);
73 }
74 ::closesocket(s);
75
76 __end:
77 ::WSACleanup();
78 system("pause");
79 return 0;
80 }
Client端代码(ClientDemo.cpp)
1 #include <WinSock2.h>
2 #include <Windows.h>
3 #include <iostream>
4 #include <string>
5 #include <sstream>
6
7 #pragma comment(lib, "WS2_32.lib")
8
9 using namespace std;
10
11 int main()
12 {
13 WORD wVersion = MAKEWORD(2, 2);
14 WSADATA WSAData;
15 ::WSAStartup(wVersion, &WSAData);
16 SOCKET s = ::socket(AF_INET, SOCK_STREAM, 0);
17 if (s == INVALID_SOCKET)
18 {
19 cout << "socket fail!" << ::WSAGetLastError() << endl;
20 ::WSACleanup();
21 return 0;
22 }
23 sockaddr_in serverAddr;
24 // inet_addr函数转化一个"aa.bb.cc.dd"类型的IP地址字符串到长整型,它是以网络字节顺序记录的IP地址,
25 // sin_addr.S_un.S_addr指定了地址联合中的此长整型
26 serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
27 serverAddr.sin_family = AF_INET;
28 serverAddr.sin_port = htons(8080);
29
30 // 客户端程序在创建套节字之后,要使用 connect 函数请求与服务器连接
31 if (::connect(s, (LPSOCKADDR)&serverAddr, sizeof(sockaddr_in)))
32 {
33 cout << "connect server fail!" << endl;
34 ::WSACleanup();
35 return 0;
36 }
37
38 char buf[256];
39 // recv 函数从对方接收数据,并存储它到指定的缓冲区。flag参数通常设置为0
40 ::recv(s, buf, 256, 0);
41 stringstream os;
42 os << "从服务器接收到数据:" << buf;
43 cout << os.str() << endl;
44
45 system("pause");
46 return 0;
47 }
TCP 协议由于可靠、稳定的特征而被用在大部分场合,但它对系统资源要求比较高。UDP
协议是一个简单的面向数据报的传输层协议,又叫用户数据报协议。它提供了无连接的、不可
靠的数据传输服务。无连接是指他不像 TCP 协议那样在通信前先与对方建立连接以确定对方
的状态。不可靠是指它直接安装指定 IP 地址和端口号将数据包发出去,如果对方不在线的话
数据就可能丢失。UDP 协议编程流程如下:
1.服务器端
(1)创建套节字(socket) 。
(2)绑定 IP 地址和端口(bind) 。
(3)收发数据(sendto/recvfrom) 。
(4)关闭连接 (closesocket) 。
2.客户端
(1)创建套节字(socket) 。
(2)收发数据(sendto/recvfrom) 。
(3)关闭连接(closesocket) 。
UDP 协议用于发送和接收数据的函数是 sendto 和 recvfrom。它们的原形如下。
int sendto (
SOCKET s, // 用来发送数据的套节字
const char FAR * buf, // 指向发送数据的缓冲区
int len, // 要发送数据的长度
int flags, // 一般指定为0
const struct sockaddr * to, // 指向一个包含目标地址和端口号的sockaddr_in结构
int tolen // 为 sockaddr_in 结构的大小
);
同样 UDP 协议接收数据也需要知道通信对端的地址信息。
int recvfrom (SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen);
这个函数不同于 recv 函数的是多出来的最后两个参数,from 参数是指向 sockaddr_in 结
构的指针,函数在这里返回数据发送方的地址,fromlen 参数用于返回前面的 sockaddr_in 结构
的长度。
与 TCP 协议相比,UDP 协议简单多了,编程细节就不详细介绍了。
TCPClient封装类(tcpClient.hpp):
1 #ifndef __TCP_CLIENT_H__
2 #define __TCP_CLIENT_H__
3
4 #include <winsock2.h>
5 #include <stdio.h>
6 #include <iostream>
7
8 class CTcpClient
9 {
10 public:
11 std::string m_strErrInfo;//错误信息
12
13 CTcpClient()
14 {
15 WSAData wsaData;
16 if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0)
17 {
18 m_strErrInfo = "WSAStartup失败";
19 printf(m_strErrInfo.c_str());
20 }
21 if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
22 {
23 m_strErrInfo = "WSAStartup SOCKET版本不对";
24 printf(m_strErrInfo.c_str());
25 }
26 }
27
28 ~CTcpClient()
29 {
30 WSACleanup();
31 }
32
33 int SendData(const char *pAddr, const char *pPort
34 , int iSendTimeOut, int iRecvTimeOut
35 , const char *pSendData, int nSendLen
36 , char *pRecvData, int nRecevLen)
37 {
38 int iTimeOut;
39 struct sockaddr_in addrServer;
40 m_strErrInfo="";
41 int nRet = 0;
42
43 //创建SOCKET
44 SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0);
45 do
46 {
47 if(sockClient == INVALID_SOCKET)
48 {
49 m_strErrInfo = "socket创建失失败";
50 nRet = -1;
51 break;
52 }
53 //连接到服务器
54 memset(&addrServer,0,sizeof(sockaddr_in));
55 addrServer.sin_family = AF_INET;
56 addrServer.sin_addr.s_addr = inet_addr(pAddr);
57 addrServer.sin_port = htons(atoi(pPort));
58
59 if(connect(sockClient,(const struct sockaddr *)&addrServer,sizeof(sockaddr)) != 0)
60 {
61 nRet = -2;
62 m_strErrInfo = "连接到服务器失败.";
63 break;
64 }
65 //设置发送超时
66 iTimeOut = iSendTimeOut;
67
68 if(::setsockopt(sockClient,SOL_SOCKET,SO_SNDTIMEO,(char *)&iTimeOut,sizeof(iTimeOut))==SOCKET_ERROR)
69 {
70 m_strErrInfo = "setsockopt失败";
71 nRet = -3;
72 break;
73 }
74 //设置接收超时
75 iTimeOut = iRecvTimeOut;
76
77 if(::setsockopt(sockClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&iTimeOut,sizeof(iTimeOut))==SOCKET_ERROR)
78 {
79 m_strErrInfo = "setsockopt失败";
80 nRet = -4;
81 break;
82 }
83 //发送请求
84 if(send(sockClient, pSendData, nSendLen * sizeof(char), 0) <= 0)
85 {
86 m_strErrInfo = "发送失败.";
87 nRet = -5;