TCP与UDP
UDP头:
包括源端口、目的地端口、用户数据包长度,检验和 数据。
typedef struct _UDP_HEADER
{
unsigned short m_usSourPort; // 源端口号16bit
unsigned short m_usDestPort; // 目的端口号16bit
unsigned short m_usLength; // 数据包长度16bit
unsigned short m_usCheckSum; // 校验和16bit
}__attribute__((packed))UDP_HEADER, *PUDP_HEADER;
IP头:
UDP与TCP的区别
TCP | UDP |
有链接 | 无链接 |
可靠传输(使用流量控制和拥塞控制) | 不可靠传输(不使用流量控制和用三个控制) |
只能一对一 | 一对一,一对多或者多对多 |
面向字节流 | 面向报文 |
首部大小20~60字节 | 首部大小8字节 |
SOCK_UDP
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
// man 7 ip
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
// 大小端转化 host to net short
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.203.128");
int ret = bind(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind");
exit(1);
}
socklen_t len = sizeof(cli);
while(1)
{
char buf[512]={0};
recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);
}
close(sockfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
// 大小端转化 host to net short
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.203.128");
while(1)
{
char buf[512]="hello,this is udp test";
sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
bzero(buf,sizeof(buf));
recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
printf("buf is %s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
网络接口
socket
int socket(int domain, int type , int protocol);
socket作用:创建套接字。
domain: 协议族---> 指定了套接字所使用的协议类型(常用的协议族包括 AF_INET、AF_INET6、AF_UNIX等。其中,AF_INET 表示 IPv4 协议族,AF_INET6 表示 IPv6 协议族,AF_UNIX 表示 Unix 域协议族)
type:套接字类型---> 指定了套接字的数据传输方式(SOCK_STREAM 表示面向连接的流套接字,主要用于可靠传输数据,例如 TCP 协议。SOCK_DGRAM 表示无连接的数据报套接字,主要用于不可靠传输数据,例如 UDP 协议。)
protocol:协议类型--->指定了套接字所使用的具体的协议类型
bind
int bind( (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind作用:绑定套接字,将套接字与网络地址绑7定。
sockfd:为套接字描述符。
addr 表示绑定的地址信息。
addrlen 是绑定地址信息的长度。
sendto
发送数据(会阻塞)
int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
sendto作用:把UDP数据包发给指定地址。
s: socket套接字描述符。
buf: UDP数据报缓存地址。(把数据放在哪里存储,或者说发送的数据是什么)
len: UDP数据报长度。(发送长度)
flags: 该参数一般为0。
to: sendto()函数参数,struct sockaddr_in类型,指明UDP数据发往哪里报。
tolen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
recvfrom
接收数据(不会阻塞)
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
recvfrom()作用:从指定地址接收UDP数据报。
s: socket套接字描述符。
buf: UDP数据报缓存地址。(把数据放在哪里存储,或者说发送的数据是什么)
len: UDP数据报长度。(接收长度)
fromlen:recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。
struct sockaddr_in结构体
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
这个结构体里面包括:
sin_family //代表协议族 在socket只指AF_INET
sin_port //存储端口号
sin_addr //存储IP地址
sin_zero //是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
listen监听套接字
int listen(int sockfd, int backlog);
功能: 在套接字id上监听等待链接
返回值:成功 0 ; 失败 -1
SOCK_TCP
服务端代码:
#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);
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);
}
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);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
close(listfd);
close(conn);
return 0;
}
客户端代码:
#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);
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)
{
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);
}
close(sockfd);
return 0;
}
三次握手和四次挥手
TCP建立连接://三次握手
最开始客户端处于closed状态,服务端处于Listen状态。
第一次握手:客户端发送一个SYN主动打开传输通道,并且说明客户端的初始化序列号ISN,这时客户端处于SYN_SENT状态。
第二次握手:服务器收到客户端发送的SYN报文之后,会用自己的SYN报文作为应答,也指定了自己的初始化序列号ISN(s),同时还会发送ACK应答给客户端,(ACK = ISN + 1),表示自己收到了客户端的SYN,服务端处于SYN_RCVD状态。
第三次握手:客户端收到SYN报文之后,会发送一个ACK报文,把服务器的ISN+1作为应答ACK的值,表示接收到了服务端的SYN,此时客户端和服务端都处于ESTABLISHED状态。此时建立起了连接。
三次握手目的
确保客户端和服务端的收发功能都是正常的。
TCP终止连接://四次挥手
客户端和服务端都可以主动发起挥手动作,若客户端先发送关闭请求,则过程如下:
第一次挥手:客户端发送一个FIN报文,报文指定一个序列号,客户端变成FIN——WAIT1的状态。
第二次挥手:服务端收到FIN之后,给客户端发送ACK报文,把客户端的序列号+1作为ACK回复,此时服务端进入CLOSE_WAIT状态。
第三次挥手:服务端也要断开连接时,发送FIN报文+序列号给客户端,此时服务端进入LAST_ACK状态。
第四次挥手: 服务端收到FIN之后发送ACK = seq+1回复应答,此时客户端处于TIME_WAIT状态,等一段时间确保服务端收到自己的ACK报文之后才会进入CLOSED状态,而服务端收到应答之后直接进入CLOSED状态。
四次挥手目的
保证客户端发送的最后一个ACK报文能到达服务端。确保所有的信息都传输完,最后发送结束信号,通知对方信息已经发完了再结束。
查看网络指令(在ubuntu)
1、ifconfig 查看自己的网络配置
2、netstat -anp 查看当前活动的互联网连接
3、ping 测试网络命令
4、sudo ufw disable/enable //防火墙关闭/打开
5、sudo apt-get install wireshark //网络抓包工具 只有网络设备闭上有数据的收发就会放在这。
6、sudo wireshark //使用抓包工具