当前位置:   article > 正文

C++小程序:同一路由器下两台计算机简单通信(1/2)——服务器端_c++ socket服务器

c++ socket服务器

       同一路由器下两台电脑如何进行通信呢?这里通过小程序实例的方式介绍SOCKET结构体以及相关函数的使用。计算机通信是在服务器端与客户端之间进行,这里先介绍服务器端程序。
       我这里编辑编译软件是VS2022,使用C++空项目进行编程。在介绍程序之前需要提醒大家,如果想把这个程序放到没有VS的计算机中使用,关于编译设置应如下进行:
       1.将Debug模式改为Release模式。
       2.右键方案名 -> 配置属性 -> c/c++ -> 代码生成 -> 运行库中选 多线程(/MT)。
       3.编译后在release目录下如找不到*.exe文件,可以在release模式下运行一下程序,然后再去找文件即可。
       4.如果使用的不是VS,也请注意一下编译问题,否则运行时会报错(缺少某些dll文件)。
       程序代码及相关解释如下:
       一、头文件部分:最基本的iostream应包含进来,并使用标准命名空间免得使用函数时还得注明作用域。WinSock2.h头文件及ws2_32.lib库文件是使用SOCKET结构及相关函数必须的。另外,还包含了关于多线程的thread头文件,处理通信过程的收和发如不使用多线程方式处理,收发之间相互影响,会造成不能连续发或连续收。

  1. #include<iostream>
  2. using namespace std;
  3. #include <WinSock2.h>
  4. #pragma comment(lib,"ws2_32.lib")
  5. #include<thread>
  6. #pragma warning (disable: 4996) //解决C4996报错问题

       二、声明函数及变量
       在这里声明一个接收函数serverAccept和一个发送函数serverSend,之所以代码不多还要从主函数中拿出来另写成两个单独函数的原因,是因为这两个函数要装到单独的线程中去运行。
       需要声明全局变量pubServerSock、pubClientSock的理由也一样,主函数中声明的变量在其它线程中使用不了。

  1. void serverRecv();//声明服务器端接收函数
  2. void serverSend(); //声明服务器端发送函数
  3. SOCKET pubServerSock; //声明服务器端全局SOCKET对象
  4. SOCKET pubClientSock; //声明客户端全局SOCKET对象

        三、开始主函数代码编写,分成一下具体步骤:

  1. int main(void)
  2. {

       步骤1. 定义显示窗口。

  1. system("color 1A"); //定义窗口前景、背景颜色
  2. system("title Server Station"); //定义窗口
  3. system("mode con cols=60 lines=30");//定义窗口高宽

       步骤2. 打开网络库。打开网络库函数是WSAStartup函数,也可以理解成网络库初始化函数。在网络编程中,经常见到WSA字头,其含义:W-windows、S-socket、A-asynchronous(异步)。这个函数有两个参数,参1 系统在用的网络库版本号,右键头文件"WinSock2" 选择 "转到文档"可看到VS2022对应的网络库版本是2.2。这个版本号在计算机中是以WORD形式存储的,参数中不能直接写2.2,需要用MAKEWORD宏给转一下。参2 是一个WSADATA类型的结构体对象,这个对象需以指针形式传入,它的作用是保存一些网络库初始化信息,后边需要使用。
       打开网络库函数有返回值,成功返回0;不成功返回非0错误码。

  1. WORD thisVersion = MAKEWORD(2, 2);
  2. WSADATA SerSockData;
  3. int nRes = WSAStartup(thisVersion, &SerSockData);
  4. if(0 != nRes)
  5. {
  6. cout << "网络库打开过程失败,程序即将结束!" << endl;
  7. system("pause");
  8. return 0; //结束程序运行
  9. }

步骤3. 校验版本(这一步骤可以略去)。如果校验版本未通过,在结束程序前,也要调用清理网络库函数。

  1. if (2 != HIBYTE(SerSockData.wVersion) || 2 != LOBYTE(SerSockData.wVersion))
  2. {
  3. cout<<"网络库版本出错误,程序即将结束!"<<endl;
  4. system("pause");
  5. WSACleanup();
  6. return 0; //结束程序运行
  7. }

       步骤4. 创建服务器端SOCKET对象,完成这一步骤的函数是socket函数。socket函数有3个参数,这3个参数是按照TCP/IP通信协议要求填写,内容不能更改。
       socket函数创建成功返回一个可用的SOCKET对象,这个对象在后面的操作中要用到,它以后就代表着服务器端计算机;如果创建失败返回INVALID_SOCKET,处理方式还是调用清理网络库函数。

  1. pubServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  2. if (INVALID_SOCKET == pubServerSock)
  3. {
  4. cout << "创建服务器端SOCKET对象失败,程序即将结束!" << endl;
  5. system("pause");
  6. WSACleanup();
  7. return 0;//结束程序运行
  8. }

       步骤5. 利用bind函数将计算机IP地址、端口与前面创建的SOCKET对象绑定。这个函数有三个参数,参1 是前面创建成功的SOCKET对象;参2 是一个结构体,这个结构体中要填上本机服务器端IP地址、端口号等内容;参3 参2的字节大小。注意:函数中需要的是sockaddr*,我们填写的是sockaddr_in(这个易填),所以取址后要强转一下,因为这些内容绑定之后不能更改,所以强转时加上const。
       关于给sockaddr_in结构体对象赋值:
       a.成员1 sin_family选AF_INET是使用TCP/IP协议必须的;
       b.成员2 是端口号,可取值范围0-65535。低位的多被系统占用了所以要尽量往大了取,我这里选的是12345。选好后可以确认一下,方法:右键开始按钮->点击运行->输入cmd->DOS符号->输入netstat -ano|findstr "12345",如果没有什么显示就说明可用,如有显示说明系统已经使用了再换一个。htons是一个宏将int转换成sin_port所需格式。
       c.成员3 是服务器所在电脑的IP地址。如果是在一台电脑上模拟服务器端和客户端通信,IP地址选用"127.0.0.1"。inet_addr也是一个宏将字符串转换成规定格式。
        当在两台电脑运行时,填写服务器端电脑的实际IP地址。方法:右键开始按钮 -> 点击运行 ->输入cmd -> DOS符号 -> 输入ipconfig 选用IPv4后面的地址。
         bind函数运行成功,会将服务器的SOCKET对象与所在计算机IP地址绑定在一起,在网上这个对象就可以代表这台计算机了;如果运行失败,会返回SOCKET_ERROR,接下来先关闭服务器SOCKET对象再清理网络库,然后结束这个程序。

  1. sockaddr_in serverSockAdd;
  2. serverSockAdd.sin_family = AF_INET;
  3. serverSockAdd.sin_port = htons(12345);
  4. serverSockAdd.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //当两台电脑通信使用服务器电脑的IP地址
  5. if (SOCKET_ERROR == bind(pubServerSock, (const sockaddr*)&serverSockAdd, sizeof(serverSockAdd)))
  6. {
  7. cout << "绑定服务器IP地址、端口失败,程序即将结束!" << endl;
  8. system("pause");
  9. closesocket(pubServerSock);
  10. WSACleanup();
  11. return 0;//结束整个程序
  12. }

       步骤6. 开启监听函数listen()。监听函数有两个参数:参1 服务器端SOCKET对象,参2 挂起链接队列的最大长度。这个函数运行成功将把服务器端SOCKET对象置于侦听传入链接的状态;如果运行失败还是关闭SOCKET对象、清理网络库,然后结束程序。

  1. if (SOCKET_ERROR == listen(pubServerSock, SOMAXCONN))
  2. {
  3. cout << "服务器未进入侦听状态,程序即将结束!" << endl;
  4. system("pause");
  5. closesocket(pubServerSock);
  6. WSACleanup();
  7. return 0;//结束整个程序
  8. }

        步骤7. 使用Accept()函数创建客户端链接。在这一步骤服务器将与客户端建立联系。建立联系的方法是在服务器端建立一个客户端的SOCKET对象,通过两台计算机双方的SOCKET对象进行联系。这一步骤使用的函数是accept(),它的参数有三个:参1 服务器SOCKET对象(注意是服务器),参2 与bind函数中相同的结构,这个结构体是指向客户端的,所以不用我们填写,参3 参2的长度。
        accept()成功返回客户端的SOCKET对象(此时客户端计算机登录),并在屏幕上显示“链接成功”;失败返回INVALID_SOCKET,失败后的操作还是SOCKET对象、
清理网络库,然后结束程序。

  1. sockaddr_in clientSockAdd;
  2. int len = sizeof(clientSockAdd);
  3. pubClientSock = accept(pubServerSock,(sockaddr*)&clientSockAdd,&len);//struct sockaddr*前的const不能加了
  4. if (INVALID_SOCKET == pubClientSock)
  5. {
  6. cout<<"客户端链接失败,程序即将结束!"<<endl;
  7. closesocket(pubServerSock);
  8. WSACleanup();
  9. return 0;//结束整个程序
  10. }
  11. cout<<"客户端连接成功。。。"<<endl;

       步骤8. 与客户端之间接收、发送消息。全局变量及thread的作用在开始介绍头文件时,已经介绍,此处不再赘述。

  1. thread serverThread1(serverRecv);
  2. thread serverThread2(serverSend);
  3. serverThread1.join();
  4. serverThread2.join();

      步骤9. 程序结束。关闭所有SOCKET对象,清理网络库。

  1. closesocket(pubServerSock);
  2. closesocket(pubClientSock);
  3. WSACleanup();
  4. system("pause");
  5. return 0; //程序结束
  6. }

       四、子程序的实现
       1. serverRecv()是通过多线程对象调用的子程序,它的作用是接收客户端发过来的消息并进行显示。使用的函数是recv()。它有3个参数:参1 客户端SOCKET对象,参2 存储收到消息的字符数组 参3 缓冲内存大小 参4 接收模式,一般填0。recv运行正常返回收到的字节数;执行失败,返回SOCKET_ERROR,客户端下线或链接中断返回 0。

  1. void serverRecv()
  2. {
  3. while (true)
  4. {
  5. char buf[1024] = { 0 };
  6. int res = recv(pubClientSock, buf, 1024 - 1, 0);
  7. if ( SOCKET_ERROR == res || 0==res)
  8. {
  9. cout << "程序运行失败或链接中断" << endl;
  10. closesocket(pubServerSock);
  11. closesocket(pubClientSock);
  12. WSACleanup();
  13. system("pause");
  14. exit(0);
  15. }
  16. else
  17. {
  18. cout << buf << endl; //显示收到的内容
  19. }
  20. }
  21. }

      2.serverSend()也是通过多线程对象调用的子程序,它的作用是向客户端发送消息。使用的函数是send(),它的参数个数及类型同及返回值都与recv()相似。

  1. void serverSend()
  2. {
  3. while (true)
  4. {
  5. string buf;
  6. cin >> buf;
  7. int res = send(pubClientSock, buf.c_str(), strlen(buf.c_str()), 0);
  8. if (SOCKET_ERROR == res)
  9. {
  10. cout << "程序运行失败或链接中断。。。" << endl;
  11. closesocket(pubServerSock);
  12. closesocket(pubClientSock);
  13. WSACleanup();
  14. system("pause");
  15. exit(0);
  16. }
  17. }
  18. }

(接下一篇)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/589545
推荐阅读
相关标签
  

闽ICP备14008679号