服务器客户端模型:
client / server
brow / ser
b / s http
p2p
socket——tcp
1、模式 C/S 模式 ==》服务器/客户端模型
server :socket()-->bind()--->listen()-->accept()-->recv()-->close()
client :socket()-->connect()-->send()-->close();
int on = 1;
setsockopt(listfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
-
listfd
:这是你想要设置选项的套接字的文件描述符。通常,这个套接字是通过调用socket()
函数创建的,用于监听传入的连接请求(例如,在服务器程序中)。 -
SOL_SOCKET
:这是选项所在的级别。SOL_SOCKET
表示这些选项是套接字级别的,而不是特定于某个协议(如TCP或UDP)的。 -
SO_REUSEADDR
:这是要设置的选项的名称。它允许在同一个本地地址和端口上启动多个套接字。这对于开发中的测试或者服务器程序快速重启而不必等待操作系统释放端口号特别有用。 -
&on
:这是一个指向整数的指针,整数的值决定了选项的状态。在这个例子中,on
应该是一个之前被设置为非零值(通常是1)的整数,表示启用SO_REUSEADDR
选项。如果on
的值为0,则表示禁用此选项。 -
sizeof(on)
:这是on
变量的大小,以字节为单位。这是告诉setsockopt
函数on
变量的长度,确保函数可以正确地读取它的值。
服务器端:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
1.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
- SOCK_STREAM - 流式套接字:
- 这类套接字提供了面向连接的、可靠的、基于字节流的服务。它们通过TCP(传输控制协议)实现,确保数据按照发送的顺序到达接收方,并且没有数据丢失或重复。
- TCP协议在发送数据之前会先建立连接,并在数据传输结束后关闭连接。这种特性使得
SOCK_STREAM
非常适合需要可靠传输的应用,如网页服务器和客户端之间的通信。
- SOCK_DGRAM - 用户数据报套接字:
- 这类套接字提供的是无连接的、不可靠的、基于消息的服务。它们通过UDP(用户数据报协议)实现,不保证数据包的顺序、完整性或到达。
- UDP协议在发送数据之前不需要建立连接,每个数据包都是独立的,这使得
SOCK_DGRAM
非常适合对实时性要求高且能容忍数据丢失的应用,如视频流、实时游戏等。
- SOCK_RAW - 原始套接字:
- 原始套接字允许程序直接访问网络层(如IP层)的数据包。这意味着你可以发送和接收原始IP数据包,包括ICMP(Internet控制消息协议)数据包等。
- 由于
SOCK_RAW
套接字允许绕过传输层的封装,它们通常用于需要直接操作网络层协议或进行网络诊断的应用。然而,由于它们提供了较低级别的网络访问,因此使用时需要谨慎,以避免破坏网络协议或造成安全问题。
protocol 协议 ==》0 表示自动适应应用层协议。
返回值:成功 返回申请的套接字id
失败 -1;
2、int bind(int sockfd, struct sockaddr *my_addr,
socklen_t addrlen);
功能:
如果该函数在服务器端调用,则表示将参数1相关的文件描述符文件(sockfd)与参数2 指定的接口地址关联(addr),用于从该接口接受数据。
如果该函数在客户端调用,则表示要将数据从参数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、 int listen(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(,0) /send(,0) ///TCP 常用套机字读写
recvfrom()/sendto() ///UDP 常用套接字读写
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;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // 包含inet_addr等函数
#include <string.h>
#include <time.h>
// 移除不必要的类型定义,直接使用标准类型
int main(int argc, char *argv[])
{
// 监听套接字
int listfd = socket(AF_INET, SOCK_STREAM, 0);
if (listfd == -1) {
perror("socket");
exit(1);
}
struct sockaddr_in ser, cli;
memset(&ser, 0, sizeof(ser)); // 使用memset代替bzero,bzero可能在某些系统上不可用
memset(&cli, 0, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listfd, (struct sockaddr *)&ser, sizeof(ser)) == -1) { // 明确类型转换
perror("bind");
exit(1);
}
// 监听连接,设置最大连接队列长度
listen(listfd, 3);
socklen_t len = sizeof(cli);
// 通信套接字
int conn = accept(listfd, (struct sockaddr *)&cli, &len);
if (conn == -1) {
perror("accept");
exit(1);
}
while (1) {
char buf[512] = {0};
int rd_ret = recv(conn, buf, sizeof(buf), 0);
if (rd_ret <= 0) { // 0 表示对方断开连接,-1 表示错误
break;
}
time_t tm;
time(&tm);
// 注意:ctime(&tm) 返回一个指向静态字符串的指针,重复使用可能导致数据覆盖
// 这里我们创建一个新的缓冲区来存储时间戳
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&tm));
// 使用snprintf来安全地格式化字符串,避免缓冲区溢出
snprintf(buf, sizeof(buf), "%s %s", buf, time_str);
send(conn, buf, strlen(buf), 0);
}
close(listfd);
close(conn);
return 0;
// 修正了错误的函数体结束符
}
===================================================
客户端:
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;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* 提供了一些基本的数据类型 */
#include <sys/socket.h> /* 提供了对套接字(socket)的操作接口 */
#include <netinet/in.h> /* 提供了IP地址和端口号的转换函数 */
#include <netinet/ip.h> /* 提供了IP协议相关的定义,但在这个程序中可能未直接使用 */
#include <string.h> /* 提供了字符串操作函数 */
#include <time.h> /* 提供了时间处理的函数,但在这个程序中未直接使用 */
#include <arpa/inet.h> /* 提供了网络地址的转换函数,如inet_addr */
// typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
// 创建一个TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
perror("socket"); // 如果创建套接字失败,打印错误信息
exit(1); // 退出程序
}
// 初始化服务器地址结构
struct sockaddr_in ser;
bzero(&ser, sizeof(ser)); // 使用bzero清零结构体,但建议使用memset更通用
ser.sin_family = AF_INET; // 使用IPv4地址
ser.sin_port = htons(50000); // 设置端口号,htons用于主机字节序到网络字节序的转换
// 将字符串地址转换为网络字节序的地址
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
// 连接到服务器
int ret = connect(sockfd, (SA)&ser, sizeof(ser));
// 注意:修正了SA的使用,应为struct sockaddr*类型
if (-1 == ret) {
perror("connect"); // 如果连接失败,打印错误信息
exit(1); // 退出程序
}
// 循环发送和接收消息
while (1) {
char buf[512] = "hello,this is tcp test"; // 初始化发送缓冲区
send(sockfd, buf, strlen(buf), 0); // 发送消息
bzero(buf, sizeof(buf)); // 清空缓冲区以接收新消息
recv(sockfd, buf, sizeof(buf), 0); // 接收服务器响应
printf("buf :%s\n", buf); // 打印接收到的消息
sleep(1); // 等待1秒
}
// 注意:由于存在无限循环,close(sockfd)和return 0;实际上不会被执行
// 如果需要退出程序,应该在循环中添加适当的退出条件
// 关闭套接字
// close(sockfd);
// return 0;
}
练习:
使用TCP完成文件的复制:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通信套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1 ==fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{// 0 对方断开连接 -1 错误
break;
}
write(fd,buf,rd_ret);
bzero(buf,sizeof(buf));
strcpy(buf,"123");
send(conn,buf,strlen(buf),0);
}
close(fd);
close(listfd);
close(conn);
return 0;
}
clin.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int fd = open("/home/linux/1.png",O_RDONLY);
if(-1 ==fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[512]={0};
int rd_ret = read(fd,buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
send(sockfd,buf,rd_ret,0);
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
}
close(fd);
close(sockfd);
return 0;
}
TCP使用过程中会出现粘包的现象
TCP粘包是指在TCP协议传输过程中,发送方连续发送的多个小数据包在接收方被组合成一个较大的数据块,或者多个小数据包粘合在一起被接收的现象。这种现象通常是由于TCP的流式传输特性和网络传输的复杂性所导致的。以下是对TCP粘包的详细解析:
一、TCP粘包的原因
TCP的流式传输特性:TCP是一个面向流的协议,它不会保留消息的边界。在TCP看来,数据流是一串连续的无边界的字节流。因此,TCP传输层并不了解上层业务数据的具体含义,它只负责将数据按照TCP缓冲区的实际情况进行划分和重组。
缓冲区大小不一致:发送方和接收方的缓冲区大小可能不一致。当发送方发送的数据包小于TCP缓冲区的大小时,TCP可能会将多个小的数据包合并成一个大的数据包发送,以提高传输效率。接收方在接收数据时,可能无法准确地按照发送方的发送边界来接收数据包,从而导致粘包现象。
接收方处理不及时:如果接收方用户进程不及时从系统接收缓冲区中取走数据,当新的数据包到达时,它会被放置在接收缓冲区的末尾,从而导致多个数据包粘合在一起。
二、TCP粘包的表现
在接收方看来,粘包现象表现为一次性接收到多个数据包的内容,这些数据包在逻辑上应该是分开的,但在实际接收时却粘合在一起。这可能会导致接收方无法正确地解析和处理数据。
三、TCP粘包的解决方法
为了解决TCP粘包问题,可以采取以下几种方法:
消息边界:在发送的数据中增加消息边界,如在数据包之间添加特定的分隔符(如换行符、特殊字符等)。接收方根据消息边界来区分和解析每个数据包。
固定长度:固定每个数据包的长度。发送方按照固定长度发送数据包,接收方也按照固定长度接收和解析数据包。这种方法简单易行,但可能不适用于长度变化较大的数据包。
额外字段:在数据包中添加额外的字段来表示数据包的长度。接收方先读取该字段,然后根据长度读取对应数量的数据,以此分割数据包。这种方法可以灵活处理不同长度的数据包。
使用消息协议:定义自己的消息协议(如使用特定格式的消息头),在发送和接收数据时,按照协议规定的格式进行打包和拆包。这种方法可以确保数据的完整性和正确性。
使用应用层协议:使用已有的应用层协议(如HTTP、WebSocket等)来处理数据的发送和接收。这些协议内部通常都有处理粘包问题的机制。
练习:
TCP实现字典查询
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
typedef struct sockaddr* (SA);
typedef struct
{
char word[50];
char mean[256];
int ret;// 0 not find 1 find
}MSG;
int do_find(MSG* msg)
{
FILE* fp= fopen("/home/linux/dict.txt","r");
if(NULL == fp)
{
perror("");
exit(1);
}
while(1)
{
char buf[1024]={0};
if(NULL==fgets(buf,sizeof(buf),fp))
{
break;
}
char *word =NULL;
char * mean=NULL;
word = strtok(buf," ");
mean=strtok(NULL,"\r");
if(0==strcmp(word,msg->word))
{
strcpy(msg->mean,mean);
msg->ret = 1;
}
}
fclose(fp);
}
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM, 0);
if(-1 == listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = htonl(INADDR_ANY);
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
//同一时刻三次握手排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通信套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
MSG msg;
while(1)
{
bzero(&msg,sizeof(msg));
int rd_ret=recv(conn,&msg,sizeof(msg),0);
if(rd_ret<=0)
{
break;
}
do_find(&msg);
send(conn,&msg,sizeof(msg),0);
}
close(listfd);
close(conn);
return 0;
}
clin.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
typedef struct sockaddr* (SA);
typedef struct
{
char word[50];
char mean[256];
int ret;// 0 not find 1 find
}MSG;
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
//host to net long
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
while(1)
{
printf("input word");
MSG msg;
bzero(&msg,sizeof(msg));
fgets(msg.word,sizeof(msg.word),stdin);//zoo\n
msg.word[strlen(msg.word)-1]='\0';
if(0==strcmp(msg.word,"#quit"))
{
break;
}
send(sockfd,&msg,sizeof(msg),0);
bzero(&msg,sizeof(msg));
int rd_ret = recv(sockfd,&msg,sizeof(msg),0);
if(rd_ret<=0)
{
break;
}
if(0 == msg.ret )
{
printf("cant find,%s\n",msg.word);
}
else
{
printf("%s %s\n",msg.word,msg.mean);
}
}
close(sockfd);
return 0;
}
练习:
1、 同桌之间互相分工,分别编写服务器和客户端程序
可以完成简单单向发送字符串功能。
客户端可以给服务器发送字符串
2、修改以上程序,完成同桌/邻桌之间的简单聊天程序。
作业:
考虑将以上程序优化后称为一个不用协调步伐的
实时聊天程序。
1、fork().
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
ssh2
secure crt
sudo ufw disable
sudo apt-get install openssh-server openssh-client
sudo apt-get install wireshark
1、telnet 远程登录工具,默认都是系统安装。
使用格式: telnet ip地址 端口
eg: telnet 192.168.1.1 8888
注意:如果没有写端口,则默认登录23 号端口。
2、netstat 测试查看网络端口使用情况
netstat -anp
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、抓包工具 tcp.port == 50000 && tcp.ip == 192.168.0.183
5.1 wireshark ==>可视化界面
tcpdump
过滤规则:
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.102
tcp host 192.168.1.100
练习:
同桌之间用ping命令互相测试网络,用wireshark 抓包证明通信数据包存在。
将之前写的代码在指定端口启动,再次抓包,练习以上抓包过程。
sudo tcpdump -n -i eth0 -xx src or dst www.taobao.com -AXX -vv|less
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
抓192.168.0.130上面发出和接受到的数据包
sudo tcpdump -n -x src or dst 192.168.0.130
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 -p 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 从客户端向服务器发送整个文件,在服务器上存储该文件。
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 主机1 2 全部完毕
ip头
(1)版本 占4位,指IP协议的版本。通信双方使用的IP协议版本必须一致。目前广泛使用的IP协议版本号为4(即IPv4)。关于IPv6,目前还处于草案阶段。
(2)首部长度 占4位,可表示的最大十进制数值是15。请注意,这个字段所表示数的单位是32位字长(1个32位字长是4字节),因此,当IP的首部长度为1111时(即十进制的15),首部长度就达到60字节。当IP分组的首部长度不是4字节的整数倍时,必须利用最后的填充字段加以填充。因此数据部分永远在4字节的整数倍开始,这样在实现IP协议时较为方便。首部长度限制为60字节的缺点是有时可能不够用。但这样做是希望用户尽量减少开销。最常用的首部长度就是20字节(即首部长度为0101),这时不使用任何选项。
(3)区分服务 占8位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。1998年IETF把这个字段改名为区分服务DS(Differentiated Services)。只有在使用区分服务时,这个字段才起作用。
(4)总长度 总长度指首部和数据之和的长度,单位为字节。总长度字段为16位,因此数据报的最大长度为216-1=65535字节。
在IP层下面的每一种数据链路层都有自己的帧格式,其中包括帧格式中的数据字段的最大长度,这称为最大传送单元MTU(Maximum Transfer Unit)。当一个数据报封装成链路层的帧时,此数据报的总长度(即首部加上数据部分)一定不能超过下面的数据链路层的MTU值。
(5)标识(identification) 占16位。IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值赋给标识字段。但这个“标识”并不是序号,因为IP是无连接服务,数据报不存在按序接收的问题。当数据报由于长度超过网络的MTU而必须分片时,这个标识字段的值就被复制到所有的数据报的标识字段中。相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报。
(6)标志(flag) 占3位,但目前只有2位有意义。
● 标志字段中的最低位记为MF(More Fragment)。MF=1即表示后面“还有分片”的数据报。MF=0表示这已是若干数据报片中的最后一个
● 标志字段中间的一位记为DF(Don’t Fragment),意思是“不能分片”。只有当DF=0时才允许分片。
(7)片偏移 占13位。片偏移指出:较长的分组在分片后,某片在原分组中的相对位置。也就是说,相对用户数据字段的起点,该片从何处开始。片偏移以8个字节为偏移单位。这就是说,每个分片的长度一定是8字节(64位)的整数倍。
(8)生存时间 占8位,生存时间字段常用的的英文缩写是TTL(Time To Live),表明是数据报在网络中的寿命。由发出数据报的源点设置这个字段。其目的是防止无法交付的数据报无限制地在因特网中兜圈子,因而白白消耗网络资源。最初的设计是以秒作为TTL的单位。每经过一个路由器时,就把TTL减去数据报在路由器消耗掉的一段时间。若数据报在路由器消耗的时间小于1秒,就把TTL值减1。当TTL值为0时,就丢弃这个数据报。
(9)协议 占8位,协议字段指出此数据报携带的数据是使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程。
(10)首部检验和 占16位。这个字段只检验数据报的首部,但不包括数据部分。这是因为数据报每经过一个路由器,路由器都要重新计算一下首部检验和(一些字段,如生存时间、标志、片偏移等都可能发生变化)。不检验数据部分可减少计算的工作量。
(11)源IP地址 占32位。
(12)目的IP地址 占32位。
http://61.133.63.5:8405/showdatab.aspx?subid=14623
firefox update
software soucre add
add-apt-repository ppa:plasmazilla/releases
sudo apt-get update
sudo apt-get remove firefox
sudo apt-get install firefox
https://launchpad.net/~plasmazilla/+archive/ubuntu/releases