写这篇日志,并不是要记录令人眼前一亮的算法,只是为了本人健忘的脑袋做一点准备。

要进行网络通信编程,就要用到socket(套接字),下面以TCP为例展示如何利用socket通信。

要 进行socket编程,首先要为工程链接导入库文件 ws2_32.lib ,然后添加头文件 #include <Winsock2.h> ,然后在App类的InitInstance()函数里面加载套接字库,加载套接字库的代码可查看MSDN里WSAStartup函数页面下端 example的代码,在加载套接字库的代码里面有一句wVersionRequested = MAKEWORD( 2, 2 );这句是指定采用2.2版本的套接字库,可根据需要修改为其他版本的套接字库。

1.TCP下的socket通信:

TCP是面向链接的通信,通信的socket双方中必须有一个是服务器端socket,另一端是客户端socket。下面用代码来展示服务器端socket和客户端socket是如何建立链接并通信的。

服务器端连接过程:

1. 使用socket函数创建一个服务器端socket:

SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);

如上用socket函数创建了一个名字叫sockSrv的socket,socket函数的第二个参数指定了这个是什么类型的socket,如果是TCP类型的socket则为SOCK_STREAM,如果是UDP类型的socket则为SOCK_DGRAM。

2.创建一个地址结构体,为地址结构体指定地址,然后使用bind函数把socket和地址绑定:

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

如上,创建了一个名字叫addrSrv的地址结构体,addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
是 指定IP地址为本机的IP地址,addrSrv.sin_port=htons(6000);是指定端口为6000端口,一定要使用1024以上的端口 号,1024以下的端口后是系统保留的。在这里有两个函数htonl和htons,这两个函数的作用以后再说。然后使用bind函数把sockSrvt和 addrSrv绑定起来。

3.设置socket为监听模式:

listen(sockSrv,5);

使 用listen函数把sockSrv设为监听模式,第二个参数是等待连接队列的最大长度。如果设置为SOMAXCONN,那么将这个套接字设置为最大的合 理值。这个值不是在一个端口上同时可以进行连接的数目,例如:如果把参数设置为2,当有3个连接请求同时到来时,前两个连接请求被放到等待请求连接队列 中,然后程序依次为这些请求服务,而第三个连接请求就被拒绝了。对多个连接请求的处理不是同时进行的,必须完成请求连接队列中一个连接请求的连接,才能开 始进行请求连接队列中下一个连接请求的连接。

4.等待客户端的连接请求到来:

SOCKADDR_IN addrClient;

int len=sizeof(SOCKADDR);

SOCKET sockConn=accept (sockSrv,(SOCKADDR*) &addrClient ,&len);

首 先创建一个地址结构体addrClient ,当有客户端请求连接sockSrv,accept 函数就会执行,建立连接并把客户端的IP地址和端口信息记录到地址结构体addrClient 里。必须注意到,accept函数的返回值是一个socket,在上面的代码中是SOCKET sockConn,这个名字叫sockConn(名字可由程序员随便取)的socket有什么用呢?其实当成功完成连接后,与客户端socket连接的是 sockConn,而不是sockSrv,以后与客户端socket进行数据传送的socket也是sockConn,而不是sockSrv,简单的说, 服务器端socket sockSrv只负责接收连接请求和进行连接操作,当连接操作完成后,与客户端socket连接的是accpet函数返回的socket,以后与客户端 socket进行数据传输的也是这个accpet函数返回的socket。

再来看TCP客户端是怎样发起连接请求的。

客户端编程也是用到socket,因此链接导入库文件,包含头文件,加载套接字库也必须先在客户端进行。

客户端请求连接过程:

1.使用socket函数创建一个客户端socket:

SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);

2.向服务器端socket发出连接请求:

SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);

connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

上 面创建了一个名字叫addrSrv的地址结构体,然后把服务器端socket的IP地址(假设服务器端IP地址是127.0.0.1)和端口来设置这个地 址结构体,然后通过connect函数向服务器端socket发出连接请求。在设置addrSrv地址结构体的IP地址时用了一个inet_addr函 数,这个函数的作用以后再说。

由此可见,客户端socket并不需要绑定IP地址和端口,为什么服务器端socket需要 绑定而客户端socket不用绑定呢?如果服务器端socket不绑定IP和端口,客户端socket又哪知道向哪个IP地址和端口发起连接请求呢?因此 服务器端socket是必须绑定IP端口的。而客户端socket不需要用bind函数绑定端口,系统会自动为socket绑定一个随机的端口,服务器只 要用accept函数返回的socket与客户端socket交换数据就行了,如果服务器需要查询客户端socket的IP和端口,可以查看accept 函数的第二个参数记录的客户端socket地址信息。

连接的操作在这里完成了,然后是进行数据的传输:

发送数据使用send函数:

int send(

SOCKET s,                    //要发送数据的socket,例如设置为服务器端的sockConn,则数据会发送到

与sockConn相连接的客户端socket sockClient上去。相反若设置为sockClient,

则数据发送到与sockClient相连接的服务器端socket sockConn上去。

const char FAR *buf,     //要发送数据buf的地址。

int len,                           //要发送数据buf的长度

int flags                         //一般设置为0即可。

);

接收数据使用recv函数:

int recv(

SOCKET s,               //要接收数据的socket,例如设置为服务器端的sockConn,则接收与sockConn相

连接的客户端socket sockClient发送的数据。相反若设置为sockClient,则接收

与sockClient相连接的服务器端socket sockConn发送的数据。

char FAR *buf,         //要发送数据buf的地址。

int len,                     //要接收数据buf的长度

int flags                    //一般设置为0即可。

);

socket使用完毕后调用closesocket()函数关闭一个socket以回收资源。在程序关闭之前,必须调用WSACleanup函数终止对套接字库的使用,注意必须在App类(应用程序类)的析构函数中调用WSACleanup函数。

接上一篇,建立连接后,服务器端的sockConn与客户端的sockClient就连接起来并且可以互相传输数据了,使用closesocket函数关闭一个socket,socket被关闭,连接也就断开了。下面是断开连接后的各种情况。

情况1:关闭服务器端sockConn--closesocket(sockConn)之后

关闭服务器端sockConn后,对sockConn使用recv函数接收数据,recv函数会马上返回SOCKET_ERROR,对sockConn使用send函数发送数据,send函数也会马上返回SOCKET_ERROR。

之后对客户端sockClient的情况分析就有点复杂了:

1.sockClient 使用send函数发送数据。第一次send函数能成功返回发送数据的大小,并不会返回SOCKET_ERROR 。虽然sockClient成功send了数据,但sockConn是无法接收到的。但sockClient使用send函数发送数据成功仅限于第一次 send,之后使用send函数返回的都是SOCKET_ERROR 。

2.sockClient使用recv函数接收数据。如果 sockClient使用recv函数之前没有使用过send函数,那么recv函数的返回值总是0(感觉好像很奇怪,recv函数返回0,难道还有成功 接收到0个字节的数据这种说法?不明白)。直到sockClient调用过send函数之后,recv函数的返回值总是SOCKET_ERROR。

情况2:关闭客户端sockClient--closesocket(sockClient)之后

这时结果就和情况1相反:

关闭客户端sockClient后,对sockClient使用recv函数接收数据,recv函数会马上返回SOCKET_ERROR,对sockClient使用send函数发送数据,send函数也会马上返回SOCKET_ERROR。

之后对服务器端sockConn的情况分析也一样:

1.sockConn 使用send函数发送数据。第一次send函数能成功返回发送数据的大小,并不会返回SOCKET_ERROR 。虽然sockConn成功send了数据,但sockClient是无法接收到的。但sockConn使用send函数发送数据成功仅限于第一次 send,之后使用send函数返回的都是SOCKET_ERROR 。

2.sockConn使用recv函数接收数据。如果sockConn使用recv函数之前没有使用过send函数,那么recv函数总是返回0。直到sockConn调用过send函数之后,recv函数的返回值总是SOCKET_ERROR。

知 道上面两个断开连接的情况后就要考虑客户端和服务器端如何协调关闭连接。TCP连接的双方Asocket和Bsocket,Asocket想要关闭连 接,Asocket的计算机除了closesocket(Asocket)之外,还要另外通知Bsocket的计算机连接已经断开了,Bsocket的计 算机收到通知后closesocket(Bsocket)来回收资源,避免Bsocket还在哪里傻傻的接收/发送数据,如果不想这样明显通知 Bsocket的计算机连接已断开,Bsocket也可以尝试自己判断,如果Bsocket多次调用send函数总是返回SOCKET_ERROR或者 Bsocket多次调用recv函数总是返回0或SOCKET_ERROR,那就要意识到连接很可能已经断开了。

如果数据的流向是单向的,例如说数据只从Asocket流向Bsocket(类似文件传输就是这样),那么Asocket只会调用send函数,而Bsocket只会调用recv函数,这时候如果其中一方要停止数据的传输,就会有两种情况出现:

1. 如果Asocket的计算机不想发送数据而closesocket(Asocket),由于Bsocket从来不调用send函数,因此Bsocket的 recv函数总是返回0,那么Bsocket的计算机就要意识到Asocket很可能已经关闭了,让Bsocket的计算机 closesocket(Bsocket)。

2.如果Bsocket的计算机不想接收数据而 closesocket(Bsocket),这时Asocket继续调用send函数发送数据,第一次send还是成功的,但从第二次send开始就总会 返回SOCKET_ERROR,但是Asocket的计算机无法判断send函数返回SOCKET_ERROR是由Bsocket关闭造成的还是 Asocket关闭造成的(因为Asocket关闭后Asocket调用send函数也是返回SOCKET_ERROR),因此Asocket的计算机无 法判断Asocket关闭了没有,简单的解决方法是如果Bsocket的计算机不想接收数据,先不要关闭Bsocket,而是发通知给Asocket的计 算机告诉它我不想收数据了,Asocket的计算机收到通知后关闭Asockt,这样情形就回到上面情况1去了,而且也知道Asocket调用send函 数返回SOCKET_ERROR肯定是由于Asockt关闭造成的而不是由Bsocket关闭造成的。

转载于:https://www.cnblogs.com/blog5258/archive/2013/04/11/socket.html

(转)API SOCKET基础(一) TCP建立连接并通信相关推荐

  1. startupinfo为什么需要初始化_为什么 TCP 建立连接要三次握手

    为什么这么设计(Why's THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点.对具体实现造成的影响 ...

  2. mobaxterm为什么无法连接_为什么 TCP 建立连接需要三次握手

    为什么这么设计(Why's THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点.对具体实现造成的影响 ...

  3. 苦练基本功-计算机网络基础-TCP建立连接

    刚开始准备秋招的时候,我以为tcp建立连接只需要掌握三次握手就行了,但是在面试的过程中,发现仅仅知道这些是不够的.这里举一个我面拼多多的一个例子: 面试官:三次握手已经考烂了,我想问问如果第一次握手之 ...

  4. 为什么tcp不采用停等协议_为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?...

    看到了一道面试题:"为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接?",想想最近也到金三银四了,所以就查阅了相关资料,整理出来了这篇文章 ...

  5. tcp建立连接为什么需要三次握手

    这是一个看似很"简单"的问题,但貌似并没有一个官方统一的答案.搜索了相关的资料,列举出一些答案. 以下部分转载自:tcp建立连接为什么需要三次握手 在<计算机网络>一书 ...

  6. TCP建立连接的三次握手

    TCP建立连接的三次握手 TCP头的构成 TCP建立连接的过程就是三次握手,三次握手成功完成,TCP连接就建立了.在三次握手之前先看一下TCP报文中的TCP头由哪些部分组成. 上图中有几个字段需要重点 ...

  7. TCP建立连接与释放连接

    TCP建立连接与释放连接   最近复习准备<计算机网络>考试,感觉TCP协议建立连接与释放连接这两个过程比较重要,所以把自己理解的部分写下来. 1.建立连接:(三次握手) (1)客户端发送 ...

  8. 为什么TCP建立连接需要三次握手

    为什么TCP建立连接需要三次握手 很简单,因为TCP的目的是相对高效地建立可靠的连接. 虽然说2次握手,请求方就已经能够确认双方路径已经没有问题了.但是接受方这边接收到的信息却仅仅是,你发起了建立连接 ...

  9. TCP建立连接三次握手和释放连接四次握手

    TCP建立连接三次握手和释放连接四次握手     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/52535294 在谈及T ...

最新文章

  1. 信息系统项目管理师-第二三章:信息系统项目管理基础与立项管理2
  2. Django Web开发基础环境配置流程
  3. 如何通过session控制单点登录
  4. Python 创建随机mac地址(单播、组播)
  5. 【算法】希尔排序 推导方法
  6. MFC工作笔记0009---VC++中 PostMessage和SendMessage的区别
  7. 系统卡 服务器cpu 内存不足,电脑很卡 系统提示内存不足的解决办法
  8. 盲盒小程序源码下载、附赠完整图片素材源码
  9. python画马鞍面_在matlab中怎么画马鞍面?
  10. mongodb使用csv导入导出
  11. 西安电子科技大学计算机应用,西安电子科技大学计算机应用技术考研
  12. WebRTC 的音频弱网对抗之 NACK
  13. 技术分享 | show engine innodb status中Pages flushed up to 的含义
  14. [Python爬虫]爬取东方财富网公司公告需要注意的几个问题
  15. 几本经典的投资理财书
  16. ae绘图未指定错误怎么办_设计高手总结47个快捷键50个CAD使用技巧,助你神速绘图拒绝加班!...
  17. 4天4夜渡劫成功,解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问
  18. MySQL---建表添加语句
  19. 思科模拟器:ethernet channel---以太网通道
  20. 源代码防泄密解决方案

热门文章

  1. npm安装与卸载和cordova及ionic项目打包调试等相关命令总结归纳
  2. php测试插入,php – 使用Symfony测试数据库插入
  3. 国开大学计算机应用基础作业二,国家开放大学《计算机应用基础》形考作业二答案解析 (2)...
  4. c语言bfs程序讲解,面试算法--二叉树DFS/BFS实现(C语言)
  5. wamp的mysql触发器教程_wamp里的mysql怎么做出这个
  6. java数组及Arrays创建一个int 类型数组 数组元素由键盘录入,每次打印插入排序的结果(数组扩容,数组排序,键盘录入)
  7. 1. 定义方法,求出指定元素在数组中出现的次数.
  8. UNIX(多线程):28---双buffer “无锁” 设计
  9. cppcheck的安装和使用
  10. 《Python Cookbook 3rd》笔记(4.3):使用生成器创建新的迭代模式