linux-网络编程之TCP

275 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

若看到相同文章,为本人其他平台

网络编程之TCP通信:

===================================================================

1、模式 C/S 模式 ==》服务器 /客户端模型

server:socket()-->bind()--->listen()-->accept()-->recv()-->close() client:socket()-->bind()-->connect()-->send()-->close();

服务器端:

#include <sys/types.h> /*See NOTES */ #include <sys/socket.h>

int socket(int domain, int type, int protocol); 功能:程序向内核提出创建一个基于内存的套接字描述符

参数: domain 地址族, PF_INET == AF_INET ==>互联网程序 PF_UNIX == AF_UNIX ==>单机程序 type 套接字类型: SOCK_STREAM 流式套接字 ===》 TCP
SOCK_DGRAM 用户数据报套接字 ===>UDP SOCK_RAW 原始套接字 ===》 IP protocol 协议 ==》 0 表示自动适应应用层协议。

返回值:成功 返回申请的套接字 id 失败 -1;

2、 int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); 功能:如果该函数在服务器端调用,则表示将参数 1相关 的文件描述符文件与参数 2 指定的接口地址关联, 用于从该接口接受数据。

 如果该函数在客户端调用,则表示要将数据从
 参数 1所在的描述符中取出并从参数 2所在的接口
 设备上发送出去。

 注意:如果是客户端,则该函数可以省略,由默认
       接口发送数据。

参数: sockfd 之前通过 socket函数创建的文件描述符,套接字 id my_addr 是物理接口的结构体指针。表示该接口的信息。

 struct sockaddr      通用地址结构
 {
     u_short sa_family;  地址族
     char sa_data[14];   地址信息
 };

 转换成网络地址结构如下:
 struct _sockaddr_in    ///网络地址结构
 {
     u_short         sin_family; 地址族
     u_short         sin_port;  ///地址端口
     struct in_addr  sin_addr;   ///地址 IP
     char            sin_zero[8]; 占位
 };

 struct in_addr
 {
     in_addr_t s_addr;
 }

 socklen_t addrlen: 参数 2 的长度。

返回值:成功 0 失败 -1;

3、 intlisten(int sockfd, int backlog); 功能:在参数 1所在的套接字 id上监听等待链接。 参数: sockfd 套接字 id backlog 允许链接的个数。 返回值:成功 0 失败 -1;

4、 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 功能:从已经监听到的队列中取出有效的客户端链接并 接入到当前程序。 参数: sockfd 套接字 id addr 如果该值为 NULL ,表示不论客户端是谁都接入。 如果要获取客户端信息,则事先定义变量 并传入变量地址,函数执行完毕将会将客户端 信息存储到该变量中。 addrlen: 参数 2的长度,如果参数 2为 NULL,则该值 也为 NULL; 如果参数不是 NULL, &len; 一定要写成 len =sizeof(struct sockaddr); 返回值:成功 返回一个用于通信的新套接字 id; 从该代码之后所有通信都基于该 id

      失败   -1;

5、接受函数: /发送函数: 四组八个

read() /write()   ///通用文件读写,可以操作套接字。
recv() /send()      ///TCP 常用套机字读写
recvfrom()/sendto() ///UDP 常用套接字读写
recvmsg()/sendmsg() ///通用套接字读写

ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能:从指定的 sockfd套接字中以 flags方式获取长度 为 len字节的数据到指定的 buff内存中。 参数: sockfd 如果服务器则是 accept的返回值的新 fd 如果客户端则是 socket的返回值旧 fd buff 用来存储数据的本地内存,一般是数组或者 动态内存。 len 要获取的数据长度 flags 获取数据的方式, 0 表示阻塞接受。

返回值:成功 表示接受的数据长度,一般小于等于 len 失败 -1;

6、 close() ===>关闭指定的套接字 id;

=================================================== 客户端:

1、 int connect(int sockfd, const struct sockaddr*addr, socklen_t addrlen); 功能:该函数固定有客户端使用,表示从当前主机向目标 主机发起链接请求。 参数: sockfd 本地 socket创建的套接子 id addr 远程目标主机的地址信息。 addrlen: 参数 2的长度。 返回值:成功 0 失败 -1;

2、 int send(int sockfd, const void *msg, size_t len, int flags); 功能:从 msg所在的内存中获取长度为 len的数据以 flags 方式写入到 sockfd对应的套接字中。

参数: sockfd: 如果是服务器则是 accept的返回值新 fd 如果是客户端则是 sockfd的返回值旧 fd

     msg 要发送的消息
     len 要发送的消息长度
     flags 消息的发送方式。

返回值:成功 发送的字符长度 失败 -1;

练习: 1、分别编写服务器和客户端程序 可以完成简单单向发送字符串功能。 客户端可以给服务器发送字符串

2、修改以上程序,完成简单聊天程序。

作业: 考虑将以上程序优化后称为一个不用协调步伐的 实时聊天程序。

1fork().
2、两个独立实体。

===================================================== 1、客户端信息获取 accept(fd,NULL,NULL); 参数2 是客户端信息,要获取该信息需要事先定义变量。

struct sockaddr_in cliaddr; socklen_t len = sizeof(struct sockaddr);

accept(fd,(struct sockaddr*)&cliaddr,&len);

printf("cliaddr ip = %s \n",inet_ntoa(cliaddr.sin_addr)); pirntf("cliaddr port = %d\n",ntohs(cliaddr.sin_port));

2、客户端的信息bind

在socket()===>bind()===>connect();

struct sockaddr_in localaddr;

localaddr.sin_family= PF_INET; localaddr.sin_port= htons(6666);///本地发送数据端口 localaddr.sin_addr.s_addr= inet_addr("192.168.1.100") ///本机ip socklen_t len = sizeof(struct sockaddr);

int ret = bind(fd,(struct sockaddr*)&localaddr,len);

3、常见测试工具编写 3.1 如何设计一个通用的客户端测试工具,可以完成给任意服务器发送消息。
./tcp_client_test serip serport

3.2 如何设计一个通用的服务器端测试工具,可以接受任意客户端的链接信息。 ./tcp_server_test myip myport

ip= x.x.x.1 port = xx msg = xxxxxx; ip= x.x.x.2 port = xx msg = xxxxxx; ip= x.x.x.3 port = xx msg = xxxxxx;

4、复杂数据传送 4.1 从客户端向服务器发送结构体数据并从在服务器端打印输出。 4.2 从客户端向服务器发送文件内存并打印输出。 4.3 从客户端向服务器发送整个文件,在服务器上存储该文件。

=================================================================== 常用网络测试工具

telnet netstat ping arp wireshark tcpdump

1、telnet 远程登录工具,默认都是系统安装。

使用格式: telnet ip地址 端口 eg: telnet 192.168.1.1 8888 注意:如果没有写端口,则默认登录23 号端口。

2、netstat 测试查看网络端口使用情况

netstat -n ===>列出当前所有网络端口使用情况 netstat -n -t ===>列出所有TCP通信的端口信息 netstat -n -u ===>列出所有UDP通信的端口信息

netstat -n -i ===>列出默认接口上的通信信息 netstat -lnp |grep 8888 ===>查看指定端口上的通信详情

3、ping 命令 测试网路的联通状况

ping ip地址 ping 域名

4、arp 地址解析命令

arp -an ===>列出当前主机的地址ARP表 arp -d ip地址

5、抓包工具 5.1 wireshark ==>可视化界面 过滤规则: 1、根据ip地址过滤:ip.src == x.x.x.x ip.dst == x.x.x.x 2、根据端口过滤:tcp.srcport == xx; tcp.dstport == xx; udp.srcport == xx; udp.dstport == xx; 3、根据协议类型过滤: tcp udp icmp ..... 4、任意组合以上条件抓包: 如果与的关系: and ip.src == 192.168.1.100 and tcp.dstport == 9999 如果或关系 : or ip.src == 192.168.1.100 or ip.dst == 192.168.1.100

练习: 用ping命令互相测试网络,用wireshark 抓包证明通信数据包存在。 将之前写的代码在指定端口启动,再次抓包,练习以上抓包过程。

5.2 tcpdump ==》命令行 ===>www.tcpdump.com

1、tcpdump -n ===>在默认的网卡上开始抓包。 2、根据ip过滤: tcpdump -n src x.x.x.x
tcpdump -n dst x.x.x.x

3、查看包中的内容: tcpdump -n -x src x.x.x.x tcpdump -n -x dst x.x.x.x

tcpdump -n -x src x.x.x.x >xxx.log

4、根据端口过滤: tcpdump -n src port xx tcpdump -n dst port xx tcpdump -n tcp port xx tcpdump -n udp port xx tcpdump -n port xx 5、根据协议过滤: tcpdump -n -p icmp/tcp/udp

6、根据指定接口过滤: tcpdump -n -i eth0 tcpdump -n -i lo

7、根据以上各种条件组合抓包: 与关系: and 或关系: or

练习: 用tcpdump 测试所有过滤选项,查看抓到的包和 wireshark的形式是否相同。

作业: 4.1 从客户端向服务器发送结构体数据并从在服务器端打印输出。 4.2 从客户端向服务器发送文件内存并打印输出。 4.3 从客户端向服务器发送整个文件,在服务器上存储该文件。 a

==========================================================

TCP 编程之三次握手 与 四次挥手

1、TCP 是有连接的通信过程,需要三次握手建立链接。

两台主机之间的通信链路建立需要如下过程:

主机1 -----syn-----》主机2 主机1 《---ack syn--- 主机2 主机1 ----ack-----》主机2

通过抓包来验证三次握手: 1、 tcpdump -n -i lo tcp port 9999 ===>S S. . 2、 wireshark 规则: tcp.port == 9999 ===>syn syn ack ack

问题: 1、三次握手分别是在服务器和客户端的那个函数上完成。 2、如何用代码形式测试以上过程。

结论: 客户端函数:connect() 服务器函数:listen()

while(1) { recv(newfd,buff,sizeof(buff),0); recv(newfd,buff,sizeof(buff),O_NONBLOCK); }

四次挥手

主机1 --- F A ---》主机2 主机1 《---A ----- 主机2 主机1不在发送消息,但是有可能接受消息

主机1 《---F A --- 主机2 主机1 ----A ----》主机2 主机12 全部完毕。