转载自:https://blog.csdn.net/timmiy/article/details/51946093windows
https://blog.csdn.net/timmiy/article/details/52016946数组
socket由IP地址和端口号组成,能够经过TCP,UDP,IP协议实现不一样虚拟机或不一样计算机之间的通讯,效率较高。安全
TCP是面向链接的,它在进行通讯以前,须要双方先进行沟通,而后才能进行通讯。并且TCP是以数据流的方式进行数据传递,会自动的进行拆包和组包的过程。因此TCP的链接是比较可靠的,可是它的传输速度也所以相对较慢。接下来分别介绍服务端和客户端,看下如何在windows系统中用C++语言实现TCP通讯。服务器
在windows中,要想进行socket网络操做,必须包含一个名叫作WinSock2.h(或者WinSock.h),若是包含的是WinSock2.h则必须在windows.h以前,不然会产生一些重定义的编译错误。包含完头文件以后,还要连接一个库文件ws2_32.lib,完成以后,咱们就能够开始进行TCP服务端和客户端的编写了。(若是使用Visual Studio编译器运行,VS会自动生成.h文件,不须要本身手动包含和连接库文件。)网络
首先给出服务端的实现思路:socket
1.初始化socket环境 -> 2.建立服务器socket -> 3.初始化端口和ip地址调用bind进行绑定 -> 4.调用listen进行监听 -> 5.调用accept接收客户端的请求 -> 6.调用recv和send与客户端进行通讯 -> 7.调用WSACleanup及closesocket关闭网络环境和socket函数
下面是具体的实现示例程序:spa
#include <stdio.h>
#include <winsock2.h> // 必须包含windwos.h以前
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
DWORD WINAPI clientProc(LPARAM lparam) //通讯接收和发送数据过程函数(recv、send)
{
SOCKET sockClient = (SOCKET)lparam;
char buf[1024];
while (TRUE)
{
memset(buf, 0, sizeof(buf));
// 接收客户端的一条数据
int ret = recv(sockClient, buf, sizeof(buf), 0);
//检查是否接收失败
if (SOCKET_ERROR == ret)
{
printf("socket recv failed\n");
closesocket(sockClient);
return -1;
}
else
{
printf("%s\r\n", buf);
}
// 0 表明客户端主动断开链接
if (ret == 0)
{
printf("client close connection\n");
closesocket(sockClient);
return -1;
}
// 发送数据
char *p = "hello client";
ret = send(sockClient, p, strlen(p), 0);
//检查是否发送失败
if (SOCKET_ERROR == ret)
{
printf("socket send failed\n");
closesocket(sockClient);
return -1;
}
}
closesocket(sockClient);
return 0;
}
bool InitNetEnv() //网络环境初始化函数
{
// 进行网络环境的初始化操做
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed\n");
return false;
}
return true;
}
int main(int argc, char * argv[])
{
if (!InitNetEnv()) //Step1:初始化网络环境
{
return -1;
}
// Step2:初始化完成,建立一个TCP的socket
//socket(协议域,指定socket类型,指定协议)
SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//检查是否建立失败
if (sServer == INVALID_SOCKET)
{
printf("socket failed\n");
return -1;
}
printf("Create socket OK\n");
//Step3:进行绑定操做(bind)
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET; // 协议簇为IPV4的
addrServ.sin_port = htons(PORT); // 端口 由于本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
addrServ.sin_addr.S_un.S_addr = INADDR_ANY; // ip地址,INADDR_ANY表示绑定电脑上全部网卡IP
//完成绑定操做
//bind(socket描述字, 绑定给listenfd的协议地址,地址长度)
int ret = bind(sServer, (sockaddr *)&addrServ, sizeof(sockaddr));
//检查绑定是否成功
if (SOCKET_ERROR == ret)
{
printf("socket bind failed\n");
WSACleanup(); // 释放网络环境
closesocket(sServer); // 关闭网络链接
return -1;
}
printf("socket bind OK\n");
// Stpe4:绑定成功,进行监听(listen)
//listen(socket描述字, socket能够排队的最大链接个数)
ret = listen(sServer, 10);
//检查是否监听成功
if (SOCKET_ERROR == ret)
{
printf("socket listen failed\n");
WSACleanup();
closesocket(sServer);
return -1;
}
printf("socket listen OK\n");
// 监听成功
sockaddr_in addrClient; // 用于保存客户端的网络节点的信息
int addrClientLen = sizeof(sockaddr_in);
while (TRUE)
{
//新建一个socket,用于客户端
SOCKET *sClient = new SOCKET;
//Step5:等待客户端的链接(accept)
//accept(服务器的描述字,指向客户端的协议地址, 协议地址的长度)
*sClient = accept(sServer, (sockaddr*)&addrClient, &addrClientLen);
if (INVALID_SOCKET == *sClient)
{
printf("socket accept failed\n");
WSACleanup();
closesocket(sServer);
delete sClient;
return -1;
}
//Step6:建立线程为客户端作数据收发
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)clientProc, (LPVOID)*sClient, 0, 0);
}
closesocket(sServer); //关闭网络环境和socket
WSACleanup();
return 0;
}
下面对代码中的实现函数进行说明。操作系统
1)首先,要进行网络操做,咱们先要进行一下网络环境的初始化。WSAStartup函数就是用来初始化网络环境的。其声明以下:.net
int WSAStartup(
WORD wVersionRequested, //版本号,通常使用2.2版本
LPWSADATA lpWSAData <span style="white-space:pre"> </span>//WSAData地址
);
函数的第二个参数,接收一个WSAData结构的指针,该结构里边包含了版本号,咱们传递的版本号会对该结构里边的版本号进行初始化。
2)初始化完成以后,咱们须要建立一个socket(套接字),这个套接字至关于管道,用于客户端和服务端的链接。调用socket函数咱们能够建立一个套接字,声明以下:
SOCKET socket(
int af, //IP协议簇
int type, //套接字类型,TCP应该用SOCK_STREAM
int protocol<span style="white-space:pre"> </span> //协议
);
其实,socket也是一个内核对象,可是它没有内核对象所拥有的明显标志,安全属性。
3)建立好套接字后呢,咱们须要告诉操做系统须要在哪一个地址和端口上进行网络操做,至关于管道通讯中绑定到标准输入输出口上。绑定的时候,须要有一个SOCKADDR_IN这个结构体,声明以下:
struct sockaddr_in{
short sin_family;//协议簇
unsigned short sin_port;//端口
struct in_addr sin_addr;//ip地址
char sin_zero[8];//为了设置和SOCKADDR结构等长的补充字节
};
还有一个SOCKADDR结构和上面这个的功能彻底同样,可是SOCKADDR这个结构里边只有两个成员,一个是协议簇,一个是14个字节的char数组,为了让咱们更好的编写代码,因而将char数组拆解成SOCKADDR_IN 中后三个成员。
初始化完端口,地址等信息后,须要调用bind函数,来完成绑定操做,声明以下:
int bind(
SOCKET s, //咱们建立的那个socket
const struct sockaddr FAR *name, //sockaddr结构指针
int namelen //sockaddr长度
);
int listen(
SOCKET s, //咱们建立的socket
int backlog //最大链接的队列长度
);
5)监听完成以后,咱们就能够进行接收客户端的链接了,咱们须要调用accept这个函数来进行接客。声明以下:
SOCKET accept(
SOCKET s, //咱们监听的那个socket
struct sockaddr FAR *addr, //咱们须要传递一个sockaddr的地址,用于保存客户端的地址
int FAR *addrlen //sockaddr的长度指针
);
6)接完客以后,咱们就能够进行通讯了,须要调用recv和send两个函数来进行收发数据,它们的声明以下:
int recv(
SOCKET s, //客户端的socket
char FAR *buf, //接收的缓冲区
int len, //缓冲区的大小
int flags //标志位,通常为0
);
int send(
SOCKET s, //客户端的socket
const char FAR *buf, //发送数据的缓冲区
int len, //缓冲区的大小
int flags //标志位,通常为0
);
7)当咱们传输完数据后,应该调用WSACleanup和closesocket来进行关闭网络环境和套接字。声明以下:
int WSACleanup (void);
int closesocket(
SOCKET s //要关闭的套接字
);
首先给出使用TCP协议在客户端的思路:
1.初始化socket环境 -> 2.建立客户端socket -> 3.调用connect链接指定的服务器 -> 4.调用recv和send与服务端进行通讯 -> 5.调用WSACleanup及closesocket关闭网络环境和socket
下面是具体的实现程序:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main(int argc, char * argv[])
{
//Step1:初始化网络环境
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed\n");
return -1;
}
// Step2:初始化完成,建立一个TCP的socket
//socket(协议域,指定socket类型,指定协议)
SOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sServer == INVALID_SOCKET)
{
printf("socket failed\n");
return -1;
}
//Step3:指定链接的服务端信息(bind)
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(PORT);
//客户端只须要链接指定的服务器地址,127.0.0.1是本机的回环地址
addrServ.sin_addr.S_un.S_addr = inet_addr("10.170.54.98");
// 服务器Bind 客户端是进行链接
//connect(客户端的socket描述字, 服务器的socket地址, 服务器地址长度)
int ret = connect(sServer, (SOCKADDR*)&addrServ, sizeof(SOCKADDR));//开始链接
if (SOCKET_ERROR == ret)
{
printf("socket connect failed\n");
WSACleanup();
closesocket(sServer);
return -1;
}
//Step4:链接成功后,就能够进行通讯了(send,recv)
char szBuf[1024];
memset(szBuf, 0, sizeof(szBuf));
sprintf_s(szBuf, sizeof(szBuf), "Hello server");
//当服务端是recv的时候,客户端就须要send,若两端同时进行收发则会卡在这里,由于recv和send都是阻塞的
ret = send(sServer, szBuf, strlen(szBuf), 0);
if (SOCKET_ERROR == ret)
{
printf("socket send failed\n");
closesocket(sServer);
return -1;
}
ret = recv(sServer, szBuf, sizeof(szBuf), 0);
if (SOCKET_ERROR == ret)
{
printf("socket recv failed\n");
closesocket(sServer);
return -1;
}
printf("%s\n", szBuf);
closesocket(sServer); //Step5:关闭已链接socket描述字
WSACleanup();
system("pause");
return 0;
}
下面对代码中的函数进行解释。
(1-2)客户端比较简单,前面的部分和服务端都基本相同(初始化、创建socket)
(3)在绑定操做上会有所差异。服务端绑定的IP地址是本机全部网卡的IP,而客户端只须要绑定一个便可,由于对客户端来讲,咱们只需链接指定的服务器。赋值完SOCKADDR_IN结构以后,服务端会调用bind函数,而客户端呢,须要调用connect函数,其声明以下:
int connect(
SOCKET s, //要进行链接的socket
const struct sockaddr FAR *name, //SOCKADDR结构地址
int namelen //SOKADDR大小
);
(5)当通讯完以后,就能够关闭链接了。文章开头讲过,当客户端和服务端刚开始链接的时候呢,二者会先进行沟通,这个沟通须要3个步骤来完成,咱们称之为3次握手,一样的关闭链接的时候,须要进行4个步骤来完成,咱们称之为4次握手。若是你是粗暴型的,直接拔网线呢,它也会完成其中的两次步骤,做为应用层开发,并不须要深究其中的原理,若感兴趣,可自行查找资料。
在两个Visual Studio中依次运行服务端及客户端程序,获得socket通讯结果以下:
相比TCP来讲,UDP相对比较简单,刚开始的时候,和TCP同样都须要先进行网络环境的初始化,即调用WSAStartup函数。而后呢,咱们也须要建立一个socket,这个socket和TCP的那个socket不一样,上篇提过TCP建立一个socket调用socket函数时,第二个参数为SOCK_STREAM,而UDP则须要给定一个SOCK_DGRAM,而后在第三个参数上给一个IPPROTO_UDP,这样咱们就建立好了一个UDP的socket。
接下来,也和TCP同样,指定SOCKADDR_IN的地址信息(端口,ip),指定完以后呢,如果客户端,则能够直接就进行通讯了,如果服务端,则还须要增长一步bind操做,当咱们调用bind函数,进行绑定后,服务端就能够和客户端进行通讯了。而TCP的服务端还有两个步骤,一个是listen,一个是accept,UDP省略了这两个步骤。
首先给出使用UDP协议实现socket通讯的服务端的实现思路:
1.初始化socket环境 -> 2.建立服务器socket -> 3.初始化端口和ip地址调用bind进行绑定 -> 4.调用recvfrom和sendto与客户端进行通讯 -> 5.调用WSACleanup及closesocket关闭网络环境和socket
下面是具体的实现代码:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main(int argc, char* argv[])
{
//Step1:初始化网络环境
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed\n");
return -1;
}
//Step2:创建一个UDP的socket
//创建socket参数:socket(协议域,指定socket类型,指定协议)(和TCP协议后两个参数不一样,都为IP协议族)
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == SOCKET_ERROR)
{
printf("create socket failed\n");
return -1;
}
//Step3:绑定地址信息
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; // 协议簇为IPV4的
serverAddr.sin_port = htons(PORT); // 端口 由于本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // ip地址,INADDR_ANY表示绑定电脑上全部网卡IP
//bind(socket描述字, 绑定给listenfd的协议地址,地址长度)
bind(sock, (sockaddr*)&serverAddr, sizeof(sockaddr));
//Step5:与客户端进行通讯
char buf[512];
while (TRUE)
{
memset(buf, 0, 512);
// 网络节点的信息,用来保存客户端的网络信息
sockaddr_in clientAddr;
memset(&clientAddr, 0, sizeof(sockaddr_in));
int clientAddrLen = sizeof(sockaddr);
//接收客户端发来的数据
//recvfrom参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操做方式),sockaddr结构地址,sockaddr结构大小地址
//sockaddr地址用来保存从哪里发来,和发送到哪里的地址信息
int ret = recvfrom(sock, buf, 512, 0, (sockaddr*)&clientAddr, &clientAddrLen);
//inet_ntoa函数转化为ip,ntohs函数转化为端口号
printf("Recv msg:%s from IP:[%s] Port:[%d]\n", buf, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
// 发一个数据包返回给客户
//sendto参数:socket名称,发送数据的缓冲区,缓冲区大小,标志位(调用操做方式),sockaddr结构地址,sockaddr结构大小地址
sendto(sock, "Hello World!", strlen("Hello World!"), 0, (sockaddr*)&clientAddr, clientAddrLen);
printf("Send msg back to IP:[%s] Port:[%d]\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
}
return 0;
}
前面提到TCP进行数据的收发是经过recv和send两个API来进行数据的收发的。而UDP也须要两个函数,叫作recvfrom和sendto,这两个和TCP那两个有点不一样,其声明以下:
int recvfrom(
SOCKET s, //socket
char FAR* buf, <span style="white-space:pre"> </span>//接收数据的缓冲区
int len, //缓冲区的大小
int flags, //标志位,调用操做方式
struct sockaddr FAR *from, //sockaddr结构地址
int FAR *fromlen //sockaddr结构大小地址
);
int sendto(
SOCKET s, //socket
const char FAR *buf, //发送数据的缓冲区
int len, //缓冲区大小
int flags, //标志位,调用操做方式
const struct sockaddr FAR *to, //sockaddr结构地址
int tolen //sockaddr结构大小地址
);
注意,这两个函数里边有一个sockaddr结构地址,它是用来保存该数据发送者的信息的。上篇提过,TCP是面向链接的,它在通讯以前须要进行三次握手来肯定双方是否已经准备好了。所以,双方很清楚数据是从哪里来的。而UDP是面向数据包的,所以就好像寄快递同样,你必须在快递上写一张纸条,上面填好姓名,地址等信息,填好以后,接收者才知道该东西是由谁寄过来的。所以,上面两个函数提供了sockaddr结构的地址,用于保存从哪里发来的和发送到哪里的地址信息。
给出使用UDP协议实现socket通讯的客户端的示例代码:
1.初始化socket环境 -> 2.建立客户端socket -> 3.调用recvfrom和sendto与服务端进行通讯 -> 4.WSACleanup及closesocket关闭网络环境和socket
下面是具体的实现程序:
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 6000
int main(int argc, char* argv[])
{
//Step1:初始化网络环境
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf("WSAStartup failed\n");
return -1;
}
//Step2:创建一个UDP的socket
//创建socket参数:socket(协议域,指定socket类型,指定协议)(和TCP协议后两个参数不一样,都为IP协议族)
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockClient == INVALID_SOCKET)
{
printf("create socket failed\n");
return -1;
}
// 申明一个网络地址信息的结构体,保存服务器的地址信息
sockaddr_in addr = { 0 };
addr.sin_family = AF_INET; // 协议簇为IPV4的
addr.sin_port = htons(PORT); // 端口 由于本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序
addr.sin_addr.S_un.S_addr = inet_addr("10.170.54.98"); // 服务器的ip地址
//Step3:与服务端进行通讯
char buf[] = "client test!";
//发送数据
//sendto参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操做方式),sockaddr结构地址,sockaddr结构大小地址
int dwSent = sendto(sockClient, buf, strlen(buf), 0, (SOCKADDR *)&addr, sizeof(SOCKADDR));
if (dwSent == 0)
{
printf("send %s failed\n", buf);
return -1;
}
printf("send msg:%s\n", buf);
char recvBuf[512];
memset(recvBuf, 0, 512);
sockaddr_in addrSever = { 0 };
int nServerAddrLen = sizeof(sockaddr_in);
// 接收数据
//recvfrom参数:socket名称,接收数据的缓冲区,缓冲区大小,标志位(调用操做方式),sockaddr结构地址,sockaddr结构大小地址
int dwRecv = recvfrom(sockClient, recvBuf, 512, 0, (SOCKADDR *)&addrSever, &nServerAddrLen);
printf("Recv msg from server : %s\n", recvBuf);
//Step4:关闭SOCKET链接
closesocket(sockClient);
//清理网络环境
WSACleanup();
system("pause");
return 0;
}
在两个Visual Studio中依次运行服务端及客户端程序,获得socket通讯结果以下: