傻瓜教程:从头说明Windows Socket网络编程(服务器端与客户端通信,附代码与运行截图,含遇到错误解决办法)

1 准备知识

1.1 环境

    Windows环境下使用的VS2019.

1.2 需要的库

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

#pragma comment(lib,"ws2_32.lib")//加载ws2_32.ddl

1.3 sockaddr_in结构说明

    可以配合底下全部源码看,更容易懂。

struct sockaddr_in{
    short sin_family;       //网络中标识不同设备使用的地址类型,对于IP类型为AF_INET
    unsigned short sin_port;//Socket对应的端口号
    IN_ADDR sin_addr;       //一个对IP进行封装的结构
    char sin_zer0[8];       //用来填充结构的数组,字符全为0,让sockaddr与sockaddr_in两个数据结构保持大小相同
    }

1.4 WSAStartup()函数

    使用WSAStartup()函数进行初始化,函数原型如下:

int WSAStartup(WORD sockVersion,LPWSADATA lpWSAData);

    说明:sockVersion表示WinSock版本,应该用的就是2.2版本,lpWSAData是一个WSADATA结构的指针,记录的Windows套接字的相关信息。WSAStartup()调用成功返回0;

1.5 WSADATA

WSADATA wsa;

    说明:用来存储被WSAStartup()函数调用后的套接字数据。
我去,才写这么点,昨天没睡好,困了我的妈呀,啊呀~~呀呀呀

1.6 socket函数

SOCKET socket(int af,int type,int procotal);

说明:
af:地址描述,目前只提供AF_INET地址格式;
type:套接字类型,分为三种:
    SOCK_STREAM:创建面向连接的流式套接字(运用TCP/IP协议);
    SOCK_DGRAM:创建无连接的数据报套接字(运用UDP协议);
    SOCK_RAM:创建原始套接字。
protocal:通信协议,如果用户不指定,可以设为0。
返回值:如果成功返回socket对象,如果不成功返回INVALID_SOCKET。

1.7 bind函数

int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);

说明:
    s:一个套接字对象;
    name:一个sockaddr结构指针。该地址中包含了要结合的地址和端口号,如果用户不在意地址和端口的值,可以设置为INADDR_ANY,即端口号为0,它可以被自动设为合适的值;
    namelen:确定name缓冲区的长度。
    返回值:成功返回0;失败返回SOCK_ERROR。

1.8 listen函数

int listen(SOCKET s,int backlog);

说明:
    该函数用于将套接字设为监听模式,建立一个监听队列来接收客户端的连接请求。对于流式套接字,必须处于监听模式下才能够接收客户端套接字的连接。
    backlog:可以接受连接的最大数量。
    返回值:同上。

1.9 accept函数

SOCKET accept(SOCKET s,struct sockaddr FAR* addr,addrlen);

    s:套接字对象,应处于监听状态;
    addr:一个sockaddr in指针;
    addrlen:用于接收参数addr的长度;
    返回值:成功返回一个SOCKET对象,不成功返回一个INVALID_SOCKET对象。

1.9 connet函数

int connect(SOCKET s,const struct FAR* name,namelen);

    s:一个套接字;
    套接字s想要连接的主机地址和端口号;
    name:name缓冲区长度;
    返回值:成功0;失败SOCKET_ERROR。

1.10 send函数

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

    s:一个套接字对象;
    buf:存放要发送数据的缓冲区;
    len:缓冲区的长度;
    flags:函数的调用方式,一般设置为NULL;
    返回值:成功返回发送资料长度,失败返回SOCKET_ERROR。

1.11 recv函数

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

说明:
    s:一个套接字对象;
    buf:接受数据的缓冲区;
    len:缓冲区长度;
    flags:解释同上。
    返回值:成功返回接收资料长度,失败返回SOCKET_ERROR。

1.12 UDP方式

sendto:

int sendto(SOCKET s,const char FAR* buf,int len,int flags,
            const struct sockaddr FAR* to,int tolen);

说明:
    重复不解释
    to(可选)指针,指向目的套接字的地址;
    tolength:to所指的地址长度;
    返回值,成功返回发送长度,失败SOCKET_ERROR。
recvfrom:

int recvform(SOCKET s,char FAR* buf,int len,int flags,
             struct sockaddr FAR* from,int FAR* fromlen);

    重复的不解释
    from:(可选)指针,指向装有源地址的缓冲区;
    fromlen:(可选)指针,指向from缓冲区长度;
    返回值:成功返回接受长度,失败同上。

1.13 closesocket

closesocket(SOCKET s);

    关闭套接字

1.14 WSACleanup

int WSACleanup(void);

    释放Ws_32.dll动态链接库初始化时分配的资源。

1.15 过程图

网络编程流程图

2 源代码

2.1服务器端

server_tcp.cpp

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

#pragma comment(lib,"ws2_32.lib")//加载ws2_32.ddl

using namespace std;

int main(int argc, char argv[]) {
	WORD sockVersion = MAKEWORD(2,2);//socket版本,用2.2版本
	WSADATA wsadate;                 //WSADATA对象
	SOCKET serverSocket;             //服务器端套接字
	SOCKET clientSocket;             //客户端套接字
	sockaddr_in sockAddr;            //服务器端口地址
	sockaddr_in clientAddr;          //客户端端口

	//初始化DLL
	if (WSAStartup(sockVersion, &wsadate) != 0) {
		return EXIT_FAILURE;
	}

	//创建套接字
	serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == INVALID_SOCKET) {
		cout << "创建套接字失败!"<<endl;
		return EXIT_FAILURE;
	}

	//指定服务器IP地址和端口
	sockAddr.sin_family = AF_INET;                    //使用IPV4地址
	sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//具体的IP地址
	sockAddr.sin_port = htons(2200);                  //具体的端口号

	//绑定套接字
	if(bind(serverSocket,(sockaddr*)&sockAddr,sizeof(sockAddr))==SOCKET_ERROR){
		cout << "绑定出错!"<<endl;
		return EXIT_FAILURE;
	}

	//进入监听状态,最多有10个连接
	if (listen(serverSocket, 10) == SOCKET_ERROR) {
		cout << "监听出错!"<<endl;
		return EXIT_FAILURE;
	}
	int length = sizeof(clientAddr);
	cout << "正在等待连接......"<<endl;
	clientSocket = accept(serverSocket,(sockaddr*)&clientAddr,&length);
	if (clientSocket == SOCKET_ERROR) {
		cout << "连接出错!"<<endl;
		return EXIT_FAILURE;
	}
	else {
		cout << "建立一条连接:" << inet_ntoa(clientAddr.sin_addr) << endl;
	}

	//实现客户端与服务器端通信
	while (1) {

		//接收客户端消息
		cout << "来自客户端的消息:" << endl;
		const int datalength = 100;                                           //假设收到的字符长度最大为100
		char reciveClient[datalength];                                        //用来保存接收到的消息
		int client_data = recv(clientSocket, reciveClient, datalength, NULL); //以下两行用来在接收到的字符数组最后加上一个字符串结束符  '\0'
		reciveClient[client_data] = '\0';                                     //表示发送的字符串已经到了结尾
		if (client_data > 0) {                                                //打印接收到的消息
			cout << "客户端:" << reciveClient << endl;

		}

		//设置出口
		if (strcmp(reciveClient, "exit()") == 0) {                            //退出循环
			break;
		}
		//向客户端发送消息
		cout << "请写下您要发送的消息:" << endl;
		string data;                                        //用来暂时保存要发送的消息
		getline(cin,data);                                  //获取输入
		const char* sentClient;                             //设置保存输入的字符数组
		sentClient = data.c_str();                          //保存输入至sentClient中
		send(clientSocket, sentClient, data.size(), NULL);  //发送给客户端

		//设置循环出口
		if (strcmp(sentClient, "exit()") == 0) {            //退出循环
			break;
		}

	}

	//关闭工作
	closesocket(serverSocket);
	closesocket(clientSocket);
	WSACleanup();
	return 0;
}

2.2 客户端

client_tcp.cpp

#include <iostream>
#include <string>
#include <WinSock2.h>
#include <stdlib.h>

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

using namespace std;

int main() {
	WORD socketVersion = MAKEWORD(2, 2);                                  //socket版本,用的是2.2版本
	WSADATA data;                                                        //WSADATA对象
	SOCKET client_socket;    //声明定义客户端套接字,ipv4,流式套接字(tcp)
	sockaddr_in sockAddr;                                                //服务器端接口地址

	//初始化DLL
	if (WSAStartup(socketVersion, &data) != 0) {
		return EXIT_FAILURE;
	}

	//创建客户端套接字
	client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
		cout << "创建套接字失败!" << endl;
		return EXIT_FAILURE;
	}

	//指定服务器端端口和地址
	sockAddr.sin_family = AF_INET;                        //使用ipv4地址
	sockAddr.sin_port = htons(2200);                      //2200端口
	sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //连接IP地址

	//开始连接
	if (connect(client_socket, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
		cout << "连接出错!" << endl;
		return EXIT_FAILURE;
	}
	else {
		cout << "连接成功!" << endl;
	}

	//实现与服务端通信
	while (1) {

		//向服务器端发送消息
		cout << "请输入你要发送的信息:" << endl;
		string data;                                         //用来暂时保存获取输入
		getline(cin, data);                                   //从键盘获取输入
		const char* sendServer;                              //用来保存输入
		sendServer = data.c_str();                           //从键盘获取的输入复制到sentServer中
		send(client_socket, sendServer, data.size(), NULL);  //发送消息

		//设置循环出口
		if (data == "exit()") {
			break;                       //退出循环
		}
		const int datalength = 100;                                             //假设允许发送消息的最大长度为100
		char reciveServer[datalength];                                          //保存接收到的消息
		int server_data = recv(client_socket, reciveServer, datalength, NULL);  //以下两行用来在接收到的字符数组最后加上一个字符串结束符  '\0'
																				//表示发送的字符串已经到了结尾
		reciveServer[server_data] = '\0';

		//打印服务器端消息
		if (server_data > 0) {
			cout << "来自服务器端的消息:" << reciveServer << endl;
		}

		//设置循环出口
		if (strcmp(reciveServer, "exit()") == 0) {
			break;                                     //退出循环
		}
		

	}

	//关闭工作
	closesocket(client_socket);
	WSACleanup();
	//system("pause");
	return 0;
}

3 运行截图

服务器端等待链接

客户端连接成功
服务器端连接成功
服务器端消息
客户端消息

4 遇到的问题

4.1 C4996’inet_ntoa’: Use inet_ntop() or InetNtop() instead or define _WINSOCK_D

解决办法:ALT+P+P打开属性页,见下图
解决办法

4.2 如何同时运行的问题

解决办法:客户端服务器端都编译运行成功之后关闭VS,打开路径中的两个.exe后缀文件,运行即可,注意要先打开服务器端,再打开客户端。

4.3 一打开客户端客户端就闪退

解决办法:对于我个人来说,只是一个定义上出了毛病,导致客户端可以编译运行,但是会一直连接出错,并且点开.exe文件还会出现闪退的问题,原本客户端定义套接字我是这样定义的:声明定义在一起

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

这样他就会一直闪退,解决办法也很直接,声明定义分开来就可以了:

SOCKET client_socket;
client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值