本篇博客是用C语言实现基于Windows环境下的CS模型
最近在学习网络编程的相关知识,写下了这篇博客当随笔,如果你也在学习这方面的知识,希望可以帮到你;由于作者水平有限,如果本文中有不对的地方,欢迎在评论处指出。
服务端
1.创建网络库并校验版本
2.创建socket函数
3.用bind函数绑定IP地址与端口号
4.用listen函数实现监听
5.用accept函数创建客户端链接
6.用recv函数与send函数与客户端收发数据
相比于服务端,客户端的程序较为简单:
客户端
1.创建网络库并校验版本
2.创建socket函数
3.用connect函数连接服务器
4.用recv函数与send函数与服务器收发数据
下面介绍各个函数的使用方法
1.打开网络库:
  首先我们需要确定我们使用网络库的版本,然后调用WSAStartup函数。

 WORD wdVersion = MAKEWORD(2, 2);   //使用网络库的版本WSADATA wdSockMsg;                    //系统通过这个参数给我们一些配置信息int nRes = WSAStartup(wdVersion, &wdSockMsg);   //打开网络库//版本校验if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion)){//版本打开错误WSACleanup();        //关闭网络库return 0;}

2.socket函数:
  整个网络传输底层的协议体系非常复杂,而socket函数将执行流程进行了封装,socket函数就是我们调用协议体系进行通信的接口。每个客户端与服务器各有一个socket,通信的时候需要socket做参数,和谁通信就传谁的socket。

SOCKET socketSever = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建服务器socket,这
//里第一个参数为IP地址类型(IPV4),第二个参数是套接字类型,第三个参数是协议类型(TCP)

3.bind函数:
  bind函数用于绑定socket与地址和端口号,在网络传输中,一台电脑向另一台电脑传输信息时,首先通过IP地址找到另一台电脑,然后通过端口号找到另一台电脑相应的应用(QQ、微信等)。

 struct sockaddr_in severMsg;severMsg.sin_family = AF_INET;                         //地址类型severMsg.sin_port = htons(12345);                        //端口号severMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //IP地址int bres = bind(socketSever, (const struct sockaddr*)&severMsg, sizeof(severMsg));

  bind函数第一个参数是要绑定的socket(此处为服务器的socket),第二个参数为一个结构体的地址,结构体中包含地址类型、IP地址和端口号,第三个参数为参数2类型的大小。在这里我们需要强调参数2,在bind函数原型中用的是struct sockaddr结构体,但是struct sockaddr结构体赋值地址类型、IP地址和端口号极为不便,因此我们首先用struct sockaddr_in结构体分别赋值参数,然后将其强制类型转换为bind函数需要的类型。
  在这里关于自己电脑端口号的问题:理论上我们电脑的端口号为0~65535,其中0-1024位系统预留端口号,我们不能使用,因此我们能使用的端口号通常较大,在这里为大家介绍两个函数如下:

netstat -ano                     //自己电脑已经被使用的端口号
netstat -ano|findstr "端口号"    //查看某一个端口号是否被占用

  使用方法:打开命令行窗口(win+R -> cmd),输入上面函数测试即可。
  关于使用的IP地址,如果是两台电脑,这里服务器和客户端的IP地址都绑定服务器的IP地址就可以,但是限于硬件的限制,我们如果在同一台电脑上实验的话直接填“127.0.0.1”就可以,它是本地回环地址,用于本地网络测试。
4.listen函数(开始监听):
  listen函数监听是否有客户端请求连接。

int a = listen(socketSever, SOMAXCONN);

  参数1为socket,因为是服务器监听连接,所以绑定的是服务器的socket。参数2为请求等待队列的长度(如果一次有很多客户端请求连接服务器,服务器无法一次性处理全部请求就会将请求的信息加入一个队列,而第二个参数就是这个队列的长度),如果没有特殊情况,我们将第二个参数设置为SOMAXCONN即可,代表在系统允许的情况下将这个队列设置为最大。
5.accept函数:
  accept函数允许在套接字上进行传入连接尝试。
  listen函数监听客户端传来的连接,accept将客户端的信息绑定到一个socket上,也就是为客户创建一个socket,通过返回值返回给我们客户端的socket。当程序运行到accept函数处时会阻塞等待客户端传来的连接。一个accept函数只能接收来自一个客户端的连接。

 struct sockaddr_in clientMsg;int len = sizeof(clientMsg);SOCKET socketClient = accept(socketSever, (struct sockaddr *)&clientMsg, &len);

  accept函数第一个参数是服务器的socket,第二个参数和bind第二个参数格式类似,它用来将服务器的socket信息与第二个参数绑定,通过返回值返回给我们客户端的socket。
  通过上面的步骤,只要服务器接收到客户端的连接,就会和客户端连接成功,下面我们通过recv与send函数与客户端实现收发信息。
6.send函数:
  send函数用于向目标发送数据,它函数将我们的数据赋值粘贴进系统的协议发送缓冲区,计算机伺机发送出去(最大传输单元是1500字节)。

int send_a = send(socketClient, "I am sever, I have received your require;", sizeof("I am sever, I have received your require;"), 0);

  第一个参数是对方(客户端)的socket;第二个参数是给对方发送的字符串,一般将其放在数组中;第三个参数是想要发送的字节的个数,第四个参数填0就OK。
7.recv函数:
  得到指定客户端发来的消息;数据的接收都是由协议本身做的,也就是socket的底层做的,系统会有一段缓冲区,存储着接收到的数据。咱们外边调用的recv作用就是通过socket找到这个缓冲区,并把数据复制进参数2中。

     //接收函数char buf[1500] = { 0 };int res = recv(socketClient, buf, 1499, 0);

  recv函数第一个参数是对方的socket;第二个参数是一个字符数组,用于存储接收到的消息;第三个参数是想要读取的字节数,一般是参数字节数减1;第四个参数填0就OK。
8.connect函数
  连接服务器并把服务器信息与服务器socket绑定在一起。

 struct sockaddr_in si;si.sin_family = AF_INET;si.sin_port = htons(12345);si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");int connect_a = connect(socketSever, (const struct sockaddr*)&si, sizeof(si));

  connect函数的第一个参数为服务器的socket,第二个参数与bind函数的第二个参数类似,sockaddr结构体存储的地址类型、IP地址和端口号都是服务器的信息。
  至此网络通信所用到的函数介绍完毕,下面我们附上完整的通信代码:

//服务端程序
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib")
#include<WinSock2.h>
#include<string.h>int main()
{WORD wdVersion = MAKEWORD(2, 2);  //使用网络库的版本WSADATA wdSockMsg;                    //系统通过这个参数给我们一些配置信息int nRes = WSAStartup(wdVersion, &wdSockMsg);   //打开/启动网络库,只有启动了这个库,库里的函数才能使用if (0 != nRes)      //如果打开网络库出错{switch (nRes){case WSASYSNOTREADY:printf("可以重启电脑,或检查网络库");break;case WSAVERNOTSUPPORTED:printf("请更新网络库");break;case WSAEINPROGRESS:printf("Please reboot this software");break;case WSAEPROCLIM:printf("请关闭不必要的软件,以为当前网络提供充足资源");break;case WSAEFAULT:printf("参数错误");break;}}//版本校验if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))//LOBYTE是主,HIBYTE是副{//版本打开错误WSACleanup();            //关闭网络库return 0;}SOCKET socketSever = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//第一个参数是地址类型(IPV4),第二个参数是套接字类型,第三个参数是协议类型TCP//如果执行失败则返回INVALID_SOCKETif (INVALID_SOCKET == socketSever){//如果socket调用失败int a = WSAGetLastError();  //返回错误码WSACleanup();   //关闭网络库return 0;}struct sockaddr_in si;si.sin_family = AF_INET;si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");si.sin_port = htons(12345);int bind_a = bind(socketSever, (struct sockaddr*)&si, sizeof(si));/*参数1:前面创建的socket参数2:是一个结构体sockaddr(包含地址类型、端口号和IP地址)地址,官方给出结构体sockaddr不方便赋值,因此我们定义sockaddr_in分别赋值地址类型、端口号和IP地址后,强制类型转换为sockaddr参数3:参数2类型的大小          */if (SOCKET_ERROR == bind_a){//bind函数出错int a = WSAGetLastError();      //获得错误码closesocket(socketSever);        //关闭socketWSACleanup();                 //关闭网络库return 0;}//开始监听int listen_a = listen(socketSever, SOMAXCONN);if (SOCKET_ERROR == listen_a){//listen函数出错int a = WSAGetLastError();           //获得错误码closesocket(socketSever);            //关闭socketWSACleanup();                     //关闭网络库return 0;}//创建客户端链接struct sockaddr_in clientMsg;int len = sizeof(clientMsg);SOCKET socketClient = accept(socketSever, (struct sockaddr*)&clientMsg, &len);//函数运行到accept函数会阻塞,等待客户端的连接if (INVALID_SOCKET == socketClient){//如果发生错误printf("客户端连接失败\n");int a = WSAGetLastError();        //返回错误码closesocket(socketSever);        //关闭socketWSACleanup();                 //关闭网络库return 0;}printf("客户端连接成功\n");while (1){char buf[1500] = { 0 };scanf("%s", buf);int send_a = send(socketClient, buf, 1499, 0);if (SOCKET_ERROR == send_a){//如果出错int a = WSAGetLastError();          //获得错误码}int recv_a = recv(socketClient, buf, 1499, 0);if (0 == recv_a){printf("连接中断,客户端下线\n");}else if (SOCKET_ERROR == recv_a){printf("sever_recv错误码:%d", WSAGetLastError());}else{printf("传输内容:%s\n", buf);}}closesocket(socketSever);                //关闭服务端socketclosesocket(socketClient);             //关闭客户端socketWSACleanup();                          //关闭网络库return 0;}
//客户端程序
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")int main()
{WORD wdVersion = MAKEWORD(2,2);       //使用的网络库版本WSADATA wdSockMsg;                    //系统通过这个参数给我们一些配置信息int nRes = WSAStartup(wdVersion, &wdSockMsg);if (0 != nRes){switch (nRes){case WSASYSNOTREADY:printf("可以重启电脑,或检查网络库");break;case WSAVERNOTSUPPORTED:printf("请更新网络库");break;case WSAEINPROGRESS:printf("Please reboot this software");break;case WSAEPROCLIM:printf("请关闭不必要的软件,以为当前网络提供充足资源");break;case WSAEFAULT:printf("参数错误");break;}return 0;}//版本校验if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion)){//版本打开错误WSACleanup();    //关闭网络库return 0;}//创建socketSOCKET socketSever = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == socketSever){int a = WSAGetLastError();             //如果socket调用失败,返回错误码(工具 -> 错误查找)WSACleanup();                           //关闭网络库return 0;}struct sockaddr_in clientMsg;clientMsg.sin_family = AF_INET;clientMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");clientMsg.sin_port = htons(12345);int connect_a = connect(socketSever,(struct sockaddr*)&clientMsg,sizeof(clientMsg));if (SOCKET_ERROR == connect_a){//connect函数出错printf("connect错误码:%d\n", WSAGetLastError());closesocket(socketSever);      //关闭socketWSACleanup();                 //清理网络库return 0;}while (1){char buf[1500] = { 0 };//接收函数int recv_a = recv(socketSever, buf, sizeof(buf), 0);{if (0 == recv_a){printf("连接中断,客户端下线\n");}else if (SOCKET_ERROR == recv_a){printf("recv错误码:%d\n", WSAGetLastError());}else{printf("传输内容:%s\n", buf);}}//发送函数scanf("%s", buf);int send_a = send(socketSever, buf, 1499, 0);if (SOCKET_ERROR == send_a){//出现错误int a = WSAGetLastError();}}    closesocket(socketSever);return 0;
}

  基本CS模型的缺点是显而易见的,下面我们逐条陈述:

  1. 一个accept只能接受一个客户端的连接,因此上述程序一个服务器只能连接一个客户端。
  2. 当服务器执行到accept函数时,如果没有客户端连接,程序会一直阻塞,无法继续执行。
  3. 当程序执行到recv函数时,如果没有接收到消息程序也会阻塞在这里,无法继续执行。
  4. 由于上述的缺点存在,当我们启动上述两个程序,客户端成功连接服务器后,该程序只能先由向客户端发信息,然后客户端回信息,然后再由服务器向客户端发信息,交替进行。
    为了克服上述缺点,人们提出了select模型,下一篇博客我们将介绍select模型的概念。

Windows环境下用C语言实现CS模型(基于TCP协议)相关推荐

  1. Windows环境下安装Go语言

    Windows环境下安装Go语言 下载 打开Go语言中文网下载页面:https://studygolang.com/dl 按照对应平台选择下载:https://studygolang.com/dl/g ...

  2. windows环境下C语言socket编程

    最近由于实验需要,要求写一个c程序与java程序通信的软件,为了测试首先写了一个windows环境下c语言的socket(tcp)通信程序. 首先socket通信的步骤:    图一     sock ...

  3. c语言程序做成可执行文件,windows环境下C程序生成可执行文件

    windows环境下,编写C程序,生成.exe,用于操作某个文件. 包含三部分:搭建环境.程序实现.程序分析. 1.搭建程序编写和编译环境 在windows下安装Git Bash(下载页面). 安装完 ...

  4. windows下编译c语言文件路径,解决JNI在Windows环境下因长路径导致编译失败问题

    之前听一个朋友反馈LuaScriptoCore在Windows下编译会报错,今天特意跑到Windows环境下测试了一番,果然是存在问题.得到了下面的编译报错信息: Build command fail ...

  5. Windows环境下Unicode编程总结和将ANSI转换到Unicode 将Unicode转换到ANSI

    Windows环境下Unicode编程总结 UNICODE环境设置 在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下. UN ...

  6. windows环境下搭建rabbitMQ开发环境

    2019独角兽企业重金招聘Python工程师标准>>> windows环境下搭建rabbitMQ开发环境 1.下载与安装 erlang rabbitmq 是使用erlang语言开发的 ...

  7. 腾讯云CMQ消息队列在Windows环境下的使用

    版权声明:本文由李少华原创文章,转载请注明出处:  文章原文链接:https://www.qcloud.com/community/article/100 来源:腾云阁 https://www.qcl ...

  8. 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信

    http://blog.csdn.net/whuancai/article/details/11994341 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通 ...

  9. Git在windows环境下的使用教程

    前言 安装 配置 关于git使用的几个问题 后记 关于代码托管,以前用过vss和svn,看博客或论坛的时候,经常有人提到github,有很多著名的开源软件都托管在github,想来肯定不错(莫笑),当 ...

  10. windows环境下unicode编程总结

    windows环境下unicode编程总结 UNICODE环境设置 在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下. UN ...

最新文章

  1. Win32基础知识5 - Win32汇编语言006
  2. 来不及想标题了,我要去打包收藏了 | 本周值得读
  3. boost::grid_graph用法的测试程序
  4. matlab 三角形隶属函数,在MATLAB模糊逻辑工具箱中,常用的隶属函数有:
  5. xpath 解析之爬取招聘信息
  6. java实现遍历树形菜单方法——设计思路【含源代码】
  7. mysql到oracle数据迁移,mysql数据迁移到oracle
  8. 找到符合条件的索引_高频面试题:MySQL联合索引的最左前缀匹配原则
  9. php-fpm配置文件,指定session保存目录
  10. uni-app 2.2 发布,大幅度优化 H5 端性能体验 | 技术头条
  11. vue 拷贝 数组_vue源码中值得学习的方法
  12. java导出数据EXCEL的工具类(以spring-webmvc-4.0.4jar为基础)
  13. T4 生成指定DB表实体
  14. html整体字体微软雅黑,网页布局中对全局字体的最佳控制_html/css_WEB-ITnose
  15. わたしたちの田村くん
  16. 简单的数据库代理操作
  17. 翻译java语言的软件_java实现英文翻译程序
  18. arduino uno + tb6600 + 42步进电机 自制自动绕线机
  19. 图像清晰度的评价指标
  20. Symbian数据库

热门文章

  1. 覆盖率验证——代码覆盖率+功能覆盖率
  2. BlendMask 论文学习
  3. C++11中的原子操作(atomic operation)和自旋锁
  4. snkrs抽签协议获取
  5. WebService 浅析(手机号码归属地实例)
  6. 什么是代理服务器(Proxy)
  7. 视频分割软件有什么,怎么分割视频
  8. 高清视频录制工具(Bandicam)v2.1.2.740中文使用技巧
  9. 论文阅读:MPViT : Multi-Path Vision Transformer for Dense Prediction
  10. python arp断网攻击_arp断网攻击,手把手教你arp断网攻击怎么解决