1网站建设,石景山老山网站建设,施工企业质量月活动总结报告,营销策划案ppt优秀案例一、网络架构 C/S #xff08;client/server 客户端/服务器#xff09;#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序#xff0c;负责提供用户界面和交互逻辑 #xff0c;接收用户输入#xff0c;向服务器发送请求#xff0c;并展示服务…一、网络架构 C/S client/server 客户端/服务器由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序负责提供用户界面和交互逻辑 接收用户输入向服务器发送请求并展示服务器返回的结果服务器端是提供服务的程序一般部署在性能较强的计算机上负责处理数据存储、业务逻辑计算等监听特定端口等待客户端请求接收到请求后进行处理并返回结果。 B/Sbrowser/server 浏览器/服务器基于浏览器和服务器客户端通过通用的浏览器如 Chrome、Firefox、等来访问应用程序。服务器端包括 Web 服务器和数据库服务器等负责处理业务逻辑、存储和管理数据。 P2Ppeer to peer 点对点是一种去中心化的网络架构网络中的节点计算机地位对等不存在专门的中心服务器。每个节点既可以作为客户端向其他节点请求服务或资源也可以作为服务器为其他节点提供服务或资源。 二、C/S 和 B/S 对比
C/S架构B/S架构客户端需要安装专用的客户端软件无需安装客户端直接通过浏览器访问协议可自定义私有协议如游戏使用二进制协议灵活性高。基于HTTP/HTTPS 协议需遵循 Web 标准如 RESTful 接口。功能资源客户端分担部分计算压力服务器专注数据处理适合高并发、大数据量场景。所有逻辑在服务器端执行需应对大量 HTTP 请求对服务器性能要求高。
三、C/S通信流程 服务器端流程 创建套接字socket ()调用 socket() 函数创建一个套接字这是进行网络通信的基础。它会返回一个套接字描述符后续操作将基于这个描述符进行。该函数确定通信的协议族如 AF_INET 表示 IPv4、套接字类型如 SOCK_STREAM 表示 TCP 流套接字和协议通常为 0由系统根据前两个参数自动选择合适协议。绑定地址和端口bind ()使用 bind() 函数将创建的套接字与特定的 IP 地址和端口号绑定。这样服务器就能明确在哪个地址和端口上监听客户端的连接请求。需要提供套接字描述符、指向包含 IP 地址和端口号信息的结构体指针以及该结构体的大小。监听连接listen ()调用 listen() 函数使服务器进入监听状态它会为套接字创建一个等待连接的队列参数包括套接字描述符和队列的最大长度。这一步告诉系统开始接受客户端的连接请求。接受连接accept ()accept() 函数会阻塞暂停执行直到有客户端发起连接请求。一旦有客户端连接它会创建一个新的套接字描述符用于与该客户端进行通信同时返回客户端的地址信息。原来监听的套接字仍然保持监听状态继续接受其他客户端的连接。读取数据read ()服务器通过新创建的与客户端通信的套接字描述符使用 read() 函数读取客户端发送过来的数据。read() 函数从套接字接收数据并存储到指定的缓冲区中返回实际读取的字节数。处理请求服务器对读取到的数据进行相应的处理例如解析请求内容、查询数据库、进行业务逻辑计算等。写入数据write ()处理完请求后服务器使用 write() 函数将处理结果响应数据通过套接字发送回客户端。write() 函数将缓冲区中的数据写入套接字发送给客户端。再次读取数据read ()服务器可能再次调用 read() 函数等待接收客户端后续可能发送的数据比如新的请求或确认信息等。关闭连接close ()通信结束后服务器调用 close() 函数关闭与客户端通信的套接字释放相关资源。 客户端流程 创建套接字socket ()与服务器端一样客户端首先调用 socket() 函数创建一个套接字用于后续的网络通信返回套接字描述符。建立连接connect ()客户端使用 connect() 函数尝试与服务器建立连接。需要指定要连接的服务器的 IP 地址和端口号以及套接字描述符。如果服务器处于监听状态并且接受连接连接就会成功建立否则可能会返回错误。写入数据write ()连接建立后客户端调用 write() 函数向服务器发送请求数据将请求内容写入套接字发送给服务器。读取数据read ()客户端调用 read() 函数从套接字读取服务器返回的响应数据将数据存储到指定的缓冲区中。关闭连接close ()客户端完成与服务器的通信后调用 close() 函数关闭套接字释放资源。 整体交互过程 服务器端先完成初始化创建套接字、绑定、监听进入等待客户端连接的状态。客户端创建套接字并尝试连接服务器连接成功后客户端向服务器发送请求数据。服务器接收请求数据处理后向客户端发送响应数据。客户端接收响应数据双方通信结束后各自关闭套接字释放资源。 注意pid进程ID只能在本机范围内发送两台主机间无法直接发送
四、TCP通信的特点 有链接并非一上来就直接进行传输要先通过网络结点进行直接或者间接的连接然后通过创建一些函数连接起来这条链路在通信过程中一直建立着。TCP是一种可靠传输 应答机制在 TCP 通信里每次接收方收到数据都会给发送方发送一个应答报文ACK 。TCP 通过给每个数据段编号序列号接收方根据收到的数据段序列号在应答报文中用确认序号告知发送方哪些数据已正确接收。例如发送方发送了编号为 1 - 1000 的字节数据接收方若正确收到就在应答报文中带上确认序号 1001表示期望接收的下一个字节编号告知发送方 1 - 1000 已正确接收 。超时重传发送方发送数据后会设定一个超时时间若在该时间内未收到接收方的 ACK 应答报文不管是数据包丢失还是 ACK 确认应答丢失发送方都认为数据传输失败会重新发送数据 。比如网络拥堵导致数据包在传输途中滞留超过超时时间仍未到达接收方或者接收方发送的 ACK 在返回途中丢失发送方都感知不到数据已被接收就会触发超时重传 全双工通信双方都具备独立的发送和接收通道发送数据同时可接收数据。比如在网络通信中网卡支持全双工数据发送线和接收线各自独立工作 发送和接收操作瞬时同步进行。以电话通信为例通话时双方说话和听到对方声音同步语音信号在两个方向同时传输 。连续TCP 将数据视为无边界的连续字节流发送方按顺序逐字节传输接收方按序列号重组为连续的数据流。有顺序TCP 为每个字节数据分配唯一序列号接收方按序列号重组数据确保字节流顺序正确接收方通过 ACK 报文告知发送方已收到的数据序号发送方仅传输未确认的分组避免乱序。udp本身不保证。 五、“三次握手四次挥手 ” 三次握手 三次握手其实就是指建立一个TCP连接时需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口建立TCP连接并同步连接双方的序列号和确认号交换TCP窗口大小信息。 刚开始客户端处于 Closed 的状态服务端处于 Listen 状态。 在socket编程中客户端执行connect()时将触发三次握手 第一次握手客户端给服务端发一个 SYN 报文并指明客户端的初始化序列号 ISN。此时客户端处于 SYN_SENT 状态 。首部的同步位SYN1初始序号seqxSYN1的报文段不能携带数据但要消耗掉一个序号。第二次握手服务器收到客户端的 SYN 报文之后会以自己的 SYN 报文作为应答并且也是指定了自己的初始化序列号 ISN(s)。同时会把客户端的 ISN 1 作为ACK 的值表示自己已经收到了客户端的 SYN此时服务器处于 SYN_RCVD 的状态。在确认报文段中SYN1ACK1确认号ackx1初始序号seqy。第三次握手客户端收到 SYN 报文之后会发送一个 ACK 报文当然也是一样把服务器的 ISN 1 作为 ACK 的值表示已经收到了服务端的 SYN 报文此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后也处于 ESTABLISHED 状态此时双方已建立起了连接。确认报文段ACK1确认号acky1序号seqx1初始为seqx第二个报文段所以要1ACK报文段可以携带数据不携带数据则不消耗序号。 四次挥手 建立一个连接需要三次握手而终止一个连接要经过四次挥手也有将四次挥手叫做四次握手的。这由TCP的半关闭half-close造成的。所谓的半关闭其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。 TCP 连接的拆除需要发送四个包因此称为四次挥手(Four-way handshake)客户端或服务端均可主动发起挥手动作 刚开始双方都处于ESTABLISHED 状态假如是客户端先发起关闭请求。四次挥手的过程如下在socket编程中任何一方执行close()操作即可产生挥手操作 第一次挥手客户端发送一个 FIN 报文报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。 即发出连接释放报文段FIN1序号sequ并停止再发送数据主动关闭TCP连 接进入FIN_WAIT1终止等待1状态等待服务端的确认。 第二次挥手服务端收到 FIN 之后会发送 ACK 报文且把客户端的序列号值 1 作为 ACK 报文的序列号值表明已经收到客户端的报文了此时服务端处于 CLOSE_WAIT 状态。 即服务端收到连接释放报文段后即发出确认报文段ACK1确认号acku1序号 seqv服务端进入CLOSE_WAIT关闭等待状态此时的TCP处于半关闭状态 客户端到服务端的连接释放。客户端收到服务端的确认后进入FIN_WAIT2终止等待 2状态等待服务端发出的连接释放报文段。 第三次挥手如果服务端也想断开连接了和客户端的第一次挥手一样发给 FIN 报文且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 即服务端没有要向客户端发出的数据服务端发出连接释放报文段FIN1ACK1 序号seqw确认号acku1服务端进入LAST_ACK最后确认状态等待客户 端的确认。 第四次挥手客户端收到 FIN 之后一样发送一个 ACK 报文作为应答且把服务端的序列号值 1 作为自己 ACK 报文的序列号值此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态服务端收到 ACK 报文之后就处于关闭连接了处于 CLOSED 状态。 即客户端收到服务端的连接释放报文段后对此发出确认报文段ACK1sequ1 ackw1客户端进入TIME_WAIT时间等待状态。此时TCP未释放掉需要经过 时间等待计时器设置的时间2MSL后客户端才进入CLOSED状态。 特性 tcp也叫流式套接字
六、TCP服务端相关函数 socket() 创建套接字 参数 domain协议族如AF_INET表示 IPv4AF_INET6表示 IPv6。type套接字类型如SOCK_STREAM表示 TCP 流式套接字SOCK_DGRAM表示 UDP 数据报套接字。protocol通常为 0表示自动选择对应协议如 TCP 对应IPPROTO_TCP。 返回值成功返回套接字描述符非负整数失败返回-1并设置errno。 bind() 绑定地址和端口 参数 sockfdsocket()返回的套接字描述符。addr指向地址结构的指针如struct sockaddr_in。addrlen地址结构的长度如sizeof(struct sockaddr_in)。 返回值成功返回0失败返回-1。 listen() 监听连接 参数 sockfd已绑定的套接字描述符。backlog未处理连接队列的最大长度如5或SOMAXCONN。 返回值成功返回0失败返回-1。说明将套接字从主动模式转为被动模式等待客户端连接。 accept() 接受客户端连接 参数 sockfd监听套接字描述符由listen()创建。addr存储客户端地址的结构体指针可为NULL。addrlen地址结构体长度的指针需初始化为结构体大小。 返回值成功返回新的客户端套接字描述符失败返回-1。说明 阻塞直到有客户端连接到达。返回的新套接字用于与客户端通信原监听套接字继续监听 connect() 连接服务器 参数 sockfd客户端套接字描述符由socket()创建。addr服务器地址结构体指针。addrlen地址结构体长度。 返回值成功返回0失败返回-1。说明 客户端调用此函数发起与服务器的连接触发三次握手。若连接失败如服务器未监听需重新调用connect()。 send() 发送数据 参数 sockfd已连接的套接字描述符。buf待发送数据的缓冲区指针。len数据长度字节。flags通常为0或设置特殊标志如MSG_DONTWAIT表示非阻塞。 返回值成功返回实际发送的字节数失败返回-1。说明 数据可能未立即发送而是存入发送缓冲区。返回值可能小于len如网络拥塞需循环发送剩余数据。 recv() 接收数据 参数 sockfd已连接的套接字描述符。buf存储接收数据的缓冲区指针。len缓冲区最大长度。flags通常为0或设置特殊标志如MSG_PEEK表示查看但不取出数据。 返回值 成功返回实际接收的字节数。返回0表示对方已关闭连接FIN 包。返回-1表示出错如连接断开。 说明 若无数据且未关闭连接recv()默认阻塞。需循环读取直至数据全部接收尤其对于大文件。 close() 关闭套接字 参数 fd套接字描述符。 返回值成功返回0失败返回-1。说明 关闭套接字并释放资源。触发 TCP 四次挥手断开连接若为主动关闭方。 七、代码实现
服务端
#include arpa/inet.h
#include netinet/in.h
#include netinet/ip.h
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include sys/types.h /* See NOTES */
#include time.h
#include unistd.h
typedef struct sockaddr*(SA);
int main(int argc, char** argv)
{//监听套接字int listfd socket(AF_INET, SOCK_STREAM, 0);if (-1 listfd){perror(socket);return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(ser, sizeof(ser));bzero(cli, sizeof(cli));ser.sin_family AF_INET;ser.sin_port htons(50000);ser.sin_addr.s_addr inet_addr(127.0.0.1);int ret bind(listfd, (SA)ser, sizeof(ser));if (-1 ret){perror(bind);return 1;}// 三次握手的排队数 listen(listfd, 3);socklen_t len sizeof(cli);//通信套接字int conn accept(listfd, (SA)cli, len);if (-1 conn){perror(accept);return 1;}while (1){char buf[256] {0};// ret 0 实际收到的字节数//0 表示对方断开// -1 出错。ret recv(conn, buf, sizeof(buf), 0);if(ret0){break;}printf(cli:%s\n,buf);time_t tm;time(tm);struct tm * info localtime(tm);sprintf(buf,%s %d:%d:%d\n,buf, info-tm_hour,info-tm_min,info-tm_sec);send(conn,buf,strlen(buf),0);}close(conn);close(listfd);// system(pause);return 0;
}客户端
#include arpa/inet.h
#include netinet/in.h
#include netinet/ip.h
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include sys/types.h /* See NOTES */
#include time.h
#include unistd.h
typedef struct sockaddr*(SA);
int main(int argc, char** argv)
{int conn socket(AF_INET, SOCK_STREAM, 0);if (-1 conn){perror(socket);return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(ser, sizeof(ser));bzero(cli, sizeof(cli));ser.sin_family AF_INET;ser.sin_port htons(50000);ser.sin_addr.s_addr inet_addr(127.0.0.1);int ret connect(conn,(SA)ser,sizeof(ser));if(-1 ret){perror(connect);return 1;}while (1){char buf[256] {0};strcpy(buf,this is tcp test);send(conn,buf,strlen(buf),0);ret recv(conn, buf, sizeof(buf), 0);if(ret0){break;}printf(ser:%s,buf);fflush(stdout);sleep(1);}close(conn);// system(pause);return 0;
}八、黏包问题
1.什么是粘包问题
答粘包问题是指在TCP通信中发送方发送的多个独立消息在接收方被合并成一个消息接收的现象。换句话说发送方发送的多条消息在接收方被“粘”在一起导致接收方无法直接区分消息的边界。
2.粘包问题成因
TCP是面向流的协议它将数据视为一个连续的字节流不保留消息的边界。发送方发送的多个消息可能被合并到同一个TCP包中发送。接收方在读取数据时无法直接知道哪些字节属于哪条消息。
3.粘包问题的影响
接收方无法正确解析消息可能导致数据解析错误。系统的健壮性和可靠性降低尤其是在需要严格消息边界的应用中。
4.如何解决
添加分隔符在每条消息末尾添加特殊分隔符如\n或\r\n接收方通过分隔符来解析消息。固定大小每条消息的长度固定接收方根据固定长度来解析消息。自定义协议
九、练习
客户端-服务端 传送文件
服务端
#include arpa/inet.h
#include fcntl.h
#include netinet/in.h
#include netinet/ip.h
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include sys/types.h /* See NOTES */
#include time.h
#include unistd.h
typedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{//监听套接字int listfd socket(AF_INET, SOCK_STREAM, 0);if (-1 listfd){perror(socket);return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(ser, sizeof(ser));bzero(cli, sizeof(cli));ser.sin_family AF_INET;ser.sin_port htons(50000);ser.sin_addr.s_addr inet_addr(127.0.0.1);int ret bind(listfd, (SA)ser, sizeof(ser));if (-1 ret){perror(bind);return 1;}// 三次握手的排队数 listen(listfd, 3);socklen_t len sizeof(cli);//通信套接字int conn accept(listfd, (SA)cli, len);if (-1 conn){perror(accept);return 1;}int first_flag 0;int fd -1;int current_size 0;int total_size 0;while (1){PACK pack;bzero(pack, sizeof(pack));ret recv(conn, pack, sizeof(pack), 0);if (0 first_flag){first_flag 1;fd open(pack.filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);if (-1 fd){perror(open);return 1;}total_size pack.total_len;}if (0 pack.buf_len){break;}write(fd, pack.buf, pack.buf_len);current_size pack.buf_len;printf(%d/%d\n, current_size, total_size);bzero(pack, sizeof(pack));strcpy(pack.buf,go on);// send(conn,pack,sizeof(pack),0);}close(conn);close(listfd);close(fd);// system(pause);return 0;
}客户端
#include arpa/inet.h
#include fcntl.h
#include netinet/in.h
#include netinet/ip.h
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include sys/stat.h
#include sys/types.h /* See NOTES */
#include sys/types.h
#include time.h
#include unistd.htypedef struct sockaddr*(SA);
typedef struct
{char filename[256];char buf[1024];int buf_len;int total_len;
} PACK;
int main(int argc, char** argv)
{int conn socket(AF_INET, SOCK_STREAM, 0);if (-1 conn){perror(socket);return 1;}// man 7 ipstruct sockaddr_in ser, cli;bzero(ser, sizeof(ser));bzero(cli, sizeof(cli));ser.sin_family AF_INET;ser.sin_port htons(50000);ser.sin_addr.s_addr inet_addr(127.0.0.1);int ret connect(conn, (SA)ser, sizeof(ser));if (-1 ret){perror(connect);return 1;}PACK pack;strcpy(pack.filename, 1.png); // /home/linux/1.pngstruct stat st;ret stat(/home/linux/1.png, st);if (-1 ret){perror(stat);return 1;}pack.total_len st.st_size; // total sizeint fd open(/home/linux/1.png, O_RDONLY);if (-1 fd){perror(open);return 1;}while (1){pack.buf_len read(fd, pack.buf, sizeof(pack.buf));send(conn, pack, sizeof(pack), 0);if (pack.buf_len 0){break;}bzero(pack,sizeof(pack));//recv(conn,pack,sizeof(pack),0);usleep(1000*10); //10ms}close(conn);close(fd);// system(pause);return 0;
}