TCP服务器端:
int socket(int domain , int type , int protocol) domain(协议族):常用的协议族便是IPV4(PF_INET), IPV6(PF_INET6),本地通信协议的UNIX族(PF_LOCAL) type:数据传输类型;典型数据传输类型:面向连接的套接字(SOCK_STREAM),面向消息的套接字(SOCK_DGRAM) protocal:具体协议; 返回套接字文件描述符,在linux中,不区分套接字和文件,统一用文件描述符来描述;
TCP与UDP的区别:
- TCP是面向连接,UDP是无连接的传输
- TCP保证了数据传输的正确和有序,而UDP不保证
- TCP数据传输是无边界的,也就是流模式(待查),UDP传输是有边界的,采用数据报模式(待查)
- TCP需要更多的系统资源
int bind(int sockfd , const struct sockaddr* servaddr , socklen_t addrlen); 给套接字分配IP地址和端口号,将套接字和相应的IP地址和端口号绑定,失败返回-1;当没有给套接字绑定IP地址和端口号(端口号指定为0)时,bind函数被调用时会分配一个临时端口号与套接字进行绑定,但是IP地址会当TCP连接建立(客户端目的IP地址)或者在此套接字上发送UDP数据报时才会绑定IP地址;当IP地址指定为通配地址(INADDR_ANY)时,由内核来选定IP地址)
int listen(int fd , int backlog) 将套接字变为可接受连接状态;内核为监听套接字维护两个队列,一个是未完成连接队列,该队列中的每一项为客户端发送的SYN字节,另一个是已完成连接队列,存储已完成连接的客户端套接字,当accept调用时,就从该队列的队头返回,当该队列为空的时候,进程进入睡眠,等待被唤醒
int accept(int sockfd , struct sockaddr* addr , socklen_t* addrlen) ; 接受连接,返回一个全新的套接字,称为“已连接套接字”,由内核创建的一个全新的套接字,自动完成与对应的客户端套接字的连接(三路握手),由这个全新的套接字与客户端套接字进行通信
TCP客户端:
int connect(int sockfd , struct sockaddr* servaddr, socklen_t addrlen) connect函数完成两件事,如果套接字没有被绑定IP地址和端口号,内核会分配一个临时端口号,并与套接字进行绑定。如果是TCP套接字则会向服务器发起连接,进行三路握手,连接成功或失败才会返回;
基于TCP的服务器端/客户端函数调用过程:
TCP三路握手和四次挥手:
TCP为什么要三次握手?
因为客户端和服务器之间要互相同步各自的初始信号,所以每一个SYN都需要一个ACK,因为服务器端的SYN和ACK可以合并发送,所以需要三次;
TCP为什么要四次挥手?
因为客户端和服务器之间是全双工连接,每一个FIN都需要一个ACK。因为服务器端的FIN和ACK不能合并的原因是在某些情况下,客户端不再向服务器端发送数据后,服务器端可能还要向客户端发送数据,所以不能合并,所以需要四次。
为什么执行主动关闭的那端需要进入TIME_WAIT状态?
首先ACK数据包可能在网络中丢失,所以被动关闭那端可能需要重传FIN,这就需要主动关闭那端维持状态准备重传ACK;另一个原因是网络中可能存在旧连接的一些迷路的重复分组,处于TIME_WAIT状态下的连接不能建立新的化身,TIME_WAIT状态会持续2个MSL时间,足够使得旧连接的重复分组消失在网络中,避免新连接出现旧连接的重复分组;
TCP套接字的I/O缓冲
- 输入缓冲和输出缓冲单独存在
- I/O缓冲在创建套接字的时候自动生成
- 关闭套接字后仍会继续传递输出缓冲中的数据
- 关闭套接字后会丢失输入缓冲中的数据
tcp_server.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUF_SIZE 1024 void error_handling(char* m) ; int main(int argc , char* argv[]) { int listen_sock , connd_sock , readlen; struct sockaddr_in serv_addr , clnt_addr ; socklen_t addr_len; if(argc != 2) error_handling("argc error") ; listen_sock = socket(PF_INET , SOCK_STREAM , 0) ; if(listen_sock == -1) error_handlind("socket error") ; serv_addr.sin_family = AF_INET ; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY) ; serv_addr.sin_port = htons(atoi(argv[1])); if(bind(listen_sock , (struct sockaddr*)&serv_addr , sizeof(serv_addr)) == -1) error_handling("bind error") ; if(listen(listen_sock , 5) == -1) error_handling("error listen") ; addr_len = sizeof(clnt_addr) ; for(int i = 0 ; i < 5 ; i++) { connd_sock = accept(listen_sock , (struct sockaddr*)&clnt_addr , &addr_len) ; if(connd_sock == -1) error_handling("error accept") ; while(readlen = read(connd_sock , message , BUFSIZE) > 0) write(connd_sock , message , readlen) ; close(connd_sock) ; } close(listen_sock); return 0 ; }
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #define BUFSIZE 1024 void error_handling(char* m); int main(int argc , char* argv[]) { int clnt_sock; struct sockaddr_in serv_addr; if (argc != 3) error_handling("error argc"); clnt_sock = socket(PF_INET, SOCK_STREAM, 0); if (clnt_sock == -1) error_handling("error socket"); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(clnt_sock, (struct sock_addr*) & serv_addr, sizeof(serv_addr)) == -1) error_handling("connect error"); char message[BUFSIZE]; int sendlen , recvlen , readcnt; while (1) { fputs("input message(q to quit): ", stdout); fgets(message, BUFSIZE, stdin); if (message == "q\n" || message == "Q\n") break; sendlen = write(clnt_sock, message, sizeof(message)); recvlen = 0; while (recvcnt = read(clnt_sock, message + recvlen, BUF_SIZE - 1) > 0) { recvlen += recvcnt; if (recvlen >= sendlen) break; } message[recvlen] = 0; fputs(message, stdout); } close(clnt_sock); return 0; }
原文地址:https://www.cnblogs.com/mychen06/p/12580471.html
时间: 2024-11-04 00:33:27