博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
TCP客户/服务器模型
阅读量:4136 次
发布时间:2019-05-25

本文共 3873 字,大约阅读时间需要 12 分钟。

CS模型:Client /Service模型

这里写图片描述

过程描述:

类比打电话。
对于服务器,首先创建套接字,socket;之后绑定一个端口,bind;进入监听状态,listen;等到对方打电话,accept;之后一直阻塞等到客户端连接过来。
对于客户端,首先要创建套接字,之后尝试打电话,即connect,一旦拨打通了,即连接上了,开始TCP的三次握手。(详细见下面分析)

建立连接后,客户端,和服务器听过write和read进行数据请求,和数据应答。都是write发,read收。

客户端想关闭,close,然后服务器,read后,也close。

TCP三次握手

回射客户/服务器

这里写图片描述

重要函数

socket:创建套接字,用于通信。

这里写图片描述

bind

这里写图片描述

注意这里的输入参数,通用地址结构。

listen:调用socket和bind函数之后,调用accept之前调用。
这里写图片描述

listen:是将套接字从主动套接字转化为被动套接字

主动套接字:发起连接 connect
被动套接字:接收连接 accept

对于给定的监听套接字,内核要维护两个队列:

1。 已有客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程。

2。 已完成连接的队列。

accept

这里写图片描述

connect

服务器端:

//if ((conn=accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)//服务端:等待客户端接入.conn对应的是客户端发出来的套接字    //{
// ERR_EXIT("accept"); //} char recvbuf[1024]; while (1) { conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen); //如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。 cout << conn << endl; memset(recvbuf, 0, sizeof(recvbuf)); char sendBuf[50]; sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(peeraddr.sin_addr)); cout << sendBuf << endl; send(conn, sendBuf, strlen(sendBuf) + 1, 0); int ret=recv(conn, recvbuf, sizeof(recvbuf), 0); cout << recvbuf << endl; //send(conn, recvbuf, strlen(recvbuf), MSG_PEEK); } closesocket(conn);

客户端:

connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));    send(sockClient, "hello", strlen("hello") + 1, 0);    char recvBuf[50];    recv(sockClient, recvBuf, 50, 0);    printf("%s\n", recvBuf);

此时服务器端的while(1)只执行一次,为啥?是因为accept()要收到数据包才能有返回值,而客户端只发送一次,所以while(1)里的accept在收到数据包一次后,就继续接收,进入等待。但是如果把accept放到外面,里面的while(1)就一直执行了,前提是客户端必须发送一次数据包,让accept函数执行完,才执行while(1)。这个很容易理解。

我觉得好的做法就是把accept放到while(1)里。这样客户端每次有新数据时,都会打印出来。但是经过测试发现我前面的理解有点不对。

while(1)里的accept是,只要我客户端关闭连接,里面就不执行了,因为accept一直在等待。但是如果我客户端修改成下面:

while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)    {        cout << send(client_sock, sendbuf, strlen(sendbuf), 0) << endl;//发送,并输出返回字符串长度        recv(client_sock, recvbuf, sizeof(recvbuf), 0);        cout << recvbuf << endl;    }

一直从stdin中输入,服务器只能接收第一次的数据并打印出来,后面的再次输入已经无法接收了。 原因我还没搞清楚。应该和accept的机制有关。

好的做法还是把accept放到外面。

if ((conn=accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)//服务端:等待客户端接入.conn对应的是客户端发出来的套接字    {        ERR_EXIT("accept");    }    char recvbuf[1024];    while (1)    {        //conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen); //如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。        //三个参数:服务器的这边创建的套接字,接收到的数据包存储的套接字(类似于客户端的),长度peerlen。  conn:描述所接受包的SOCKET类型的值        cout << conn << endl;        memset(recvbuf, 0, sizeof(recvbuf));        char sendBuf[50];        sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(peeraddr.sin_addr));//打印的是客户端的地址,并把给地址通过sendBuf给客户端发送过去        cout << sendBuf << endl;        send(conn, sendBuf, strlen(sendBuf) + 1, 0);//给客户端发        int ret=recv(conn, recvbuf, sizeof(recvbuf), 0);        cout << recvbuf << endl;        //send(conn, recvbuf, strlen(recvbuf), MSG_PEEK);    }

另外,客户端程序进行修改:

while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)    {        cout << send(client_sock, sendbuf, strlen(sendbuf), 0) << endl;//发送,并输出返回字符串长度        recv(client_sock, recvbuf, sizeof(recvbuf), 0);        cout << recvbuf << endl;    }

上述实现的是从客户端输入,服务器端显示出来。

服务器端编程的步骤

sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字。

服务器端编程的步骤:

1:加载套接字库,创建套接字(WSAStartup()/socket());

2:绑定套接字到一个IP地址和一个端口上(bind());

3:将套接字设置为监听模式等待连接请求(listen());

4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());

5:用返回的套接字和客户端进行通信(send()/recv());

6:返回,等待另一连接请求;

7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。

客户端编程的步骤:

1:加载套接字库,创建套接字(WSAStartup()/socket());

2:向服务器发出连接请求(connect());

3:和服务器端进行通信(send()/recv());

4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。

参考:

(未完待续,有点乱)

你可能感兴趣的文章
Objective-C 基础入门(一)
查看>>
Flutter Boost的router管理
查看>>
C++模板
查看>>
【C#】如何实现一个迭代器
查看>>
【C#】利用Conditional属性完成编译忽略
查看>>
VUe+webpack构建单页router应用(一)
查看>>
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
Spring MVC中使用Thymeleaf模板引擎
查看>>
PHP 7 的五大新特性
查看>>
深入了解php底层机制
查看>>
PHP中的stdClass 【转】
查看>>
XHProf-php轻量级的性能分析工具
查看>>
OpenCV gpu模块样例注释:video_reader.cpp
查看>>
就在昨天,全球 42 亿 IPv4 地址宣告耗尽!
查看>>
Mysql复制表以及复制数据库
查看>>
Linux分区方案
查看>>
如何使用 systemd 中的定时器
查看>>
git命令速查表
查看>>
linux进程监控和自动重启的简单实现
查看>>
OpenFeign学习(三):OpenFeign配置生成代理对象
查看>>