网络编程(C++语言编程)


  TCP属于传输层协议,其中TCP提供IP环境下的数据可靠传输,它事先为要发送的数据开辟好连接通道(三次握手),然后再进行数据发送;

windows下基于TCP的服务器开发流程

开发流程

一、创建服务器套接字(create)。
二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
三、接受来自用户端的连接请求(accept)。
四、开始数据传输(send/receive)。
五、关闭套接字(closesocket)。

相关API函数介绍

1. WSAStartup函数

int WSAStartup
(
    WORD wVersionRequested,
    LPWSADATA lpWSAData
);

  使用Socket的程序在使用Socket之前必须调用WSAStartup函数。其中,第一个参数指明程序请求使用的Socket版本,第二个参数是指向lpWSADATA的指针。
案例:(WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
MAKEWORD(2,2)表示使用WINSOCK2.2版本,data用来存储系统传回的关于WINSOCK的资料。

2. socket函数
函数定义:

SOCKET socket(int domain, int type, int protocol);

domain:协议簇也,就是 IP 地址类型。常用的有 AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
type:协议类型,数据传输方式/套接字类型。常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
protocol:协议编号,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
注意:Windows 不把套接字作为普通文件对待,而是返回 SOCKET 类型的句柄

3. bind函数
  经过socket函数创建套接字后,必须将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。类似地,客户端也要用 connect() 函数建立连接。
函数定义:

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  //Windows

sock:socket 文件描述符。
addr:sockaddr 结构体变量的指针。
addrlen:addr 变量的大小。
案例:下面的代码,将创建的套接字与IP地址 127.0.0.1、端口 1234 绑定:

//创建套接字
SOCKET serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
serv_addr.sin_port = htons(1234);  //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

sockaddr_in 结构体:

struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

in_addr 结构体:

struct in_addr{
    in_addr_t  s_addr;  //32位的IP地址
};

4. listen函数
  对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态。
函数定义:

int listen(SOCKET sock, int backlog);  //Windows

sock:需要进入监听状态的套接字。
backlog:请求队列的最大长度。
被动监听:是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

5. accept() 函数
  当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
函数定义:

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  //Windows

sock:服务器端套接字。
addr:sockaddr_in 结构体变量。
addrlen:参数 addr 的长度。
注意:accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
说明:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

6. send() 和recv()函数
  由于Windows 区分普通文件和套接字,所以定义了专门的接收recv()和发送send()的函数.

send() 函数
函数定义:

int send(SOCKET sock, const char *buf, int len, int flags);

sock:要发送数据的套接字。
buf:要发送的数据的缓冲区地址。
len:要发送的数据的字节数。
flags:发送数据时的选项, flags 一般设置为 0 或 NULL。
成功则返回写入的字节数,失败则返回 -1。

recv() 函数
函数定义:

int recv(SOCKET sock, char *buf, int len, int flags);

sock:要读取的文件的描述符。
buf:要接收数据的缓冲区地址。
nbytes:要读取的数据的字节数。
flags:接收数据时的选项, flags 一般设置为 0 或 NULL。

7. closesocket()函数
函数定义:

int WSACleanup (void);

  closesocket()函数为windows下用于释放系统分配给套接字的资源(linux下为close()函数)。

8. WSACleanup()函数
  应用程序在完成对请求的Socket库的使用后,要调用WSACleanup()函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

代码

#include<WinSock2.h>
#include<WS2tcpip.h>
#include<iostream>

#pragma comment(lib,"ws2_32.lib")

int main()
{

	WSADATA data{};//存放windows socket初始化信息
	if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
	{
		std::cout << "WSAStartup func error, error num is :" << WSAGetLastError() << std::endl;
		return -1;
	}

	SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (servSock == INVALID_SOCKET)
	{
		std::cout << "socket func error, error num is : " << WSAGetLastError() << std::endl;
		WSACleanup();
		return -1;
	}
	
	sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	//htons htonl
	//htons 就是 host to net short
	servAddr.sin_port = htons(2345);
	//htonl host to net long
	inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr);
	if (bind(servSock, (sockaddr*)&servAddr, sizeof(sockaddr)) == SOCKET_ERROR)
	{
		std::cout << "bind func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		WSACleanup();
		return -1;
	}

	if (listen(servSock, 128) == -1)
	{
		std::cout << "listen func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		WSACleanup();
		return -1;
	}

	SOCKET clitsock = accept(servSock, nullptr, nullptr);
	if (clitsock == INVALID_SOCKET)
	{
		std::cout << "accept func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		WSACleanup();
		return -1;
	}

	const char* msg = "hello world";
	char recvBuf[128]{};
	if (send(clitsock, msg, strlen(msg), 0) < 0)
	{
		std::cout << "send func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		closesocket(clitsock);
		WSACleanup();
		return -1;
	}

	int recvRet = recv(clitsock, recvBuf, 128, 0);
	if (recvRet == 0)
	{
		std::cout << "client drop the connection gracefully" << std::endl;
	}
	if (recvRet < 0)
	{
		int errorNum = WSAGetLastError();
		if (errorNum == 10054)
		{
			std::cout << "客户端强制断开连接" << errorNum << std::endl;
		}
		else
		{
			std::cout << "recv func error, error num is : " << errorNum << std::endl;
			closesocket(servSock);
			closesocket(clitsock);
			WSACleanup();
			return -1;
		}
	}

	std::cout << recvBuf << std::endl;

	closesocket(servSock);
	closesocket(clitsock);

	return 0;
}

windows下基于TCP的客户端开发流程

开发流程

一、加载socket库(WSADATA wsdata;)
二、创建socket套接字(socket函数)
三、初始化客户端和服务端的地址包
四、与服务器创建连接(connect函数)
五、开始传输数据(send/recv函数)
六、关闭套接字(closesock函数)

相关API函数介绍

1. connect() 函数
connect() 函数用来建立连接
函数定义:

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  //Windows

sock:socket 文件描述符。
addr:sockaddr 结构体变量的指针。
addrlen:addr 变量的大小。

其余相关函数均与服务器模块相同,这里不再赘述。

代码

#include<WinSock2.h>
#include<WS2tcpip.h>
#include<iostream>

#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA data{};
	if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
	{
		std::cout << "WSAStartup func error, error num is :" << WSAGetLastError() << std::endl;
		return -1;
	}
	SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (servSock == INVALID_SOCKET)
	{
		std::cout << "socket func error, error num is : " << WSAGetLastError() << std::endl;
		WSACleanup();
		return -1;
	}

	//目的ip和目的端口
	sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(2345);
	inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr);
	if (connect(servSock, (sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
		std::cout << "connect func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		WSACleanup();
		return -1;
	}

	char recvBuf[128]{};
	int recvLen = recv(servSock, recvBuf, 128, 0);
	if (recvLen == 0)
	{
		std::cout << "client drop the connection gracefully" << std::endl;
	}
	if (recvLen < 0)
	{
		int errorNum = WSAGetLastError();
		if (errorNum == 10054)
		{
			std::cout << "客户端强制断开连接" << errorNum << std::endl;
		}
		else
		{
			std::cout << "recv func error, error num is : " << errorNum << std::endl;
			closesocket(servSock);
			WSACleanup();
			return -1;
		}
	}
	if (send(servSock, recvBuf, recvLen, 0) < 0)
	{
		std::cout << "send func error, error num is : " << WSAGetLastError() << std::endl;
		closesocket(servSock);
		WSACleanup();
		return -1;
	}
	send(servSock, recvBuf, recvLen, 0);

	closesocket(servSock);
	WSACleanup();

	system("pause");
	return 0;
}

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值