TCP day39
六:C/S和B/S端
C/S:Client, server
B/S:Browser server
1.cs
专用客户端 bs 通用客户端
2.协议不同
Cs
标准协议,自定义协议
Bs
http
超文本传输
3.cs
功能复杂 bs
功能弱
4.bs
资源都在ser
,有ser
发送到cli
cs
大部分资源都在cli
七:UDP和TCP特性
UDP:
- 无连接
不需要维护繁杂网络状态。网络开销小。通信双方通信过程,无法知道对方进程关闭。如果需要告知,需要发信息通知。 - 不可靠,
传输数据的过程中,会有丢包。但是实时性好。适用直播 视频传输,音频传输。
3.很容易实现一对多 。
4.可以组播,广播
TCP:
1.有链接,一次会话中,链接一直保持。如果一个断开,另外一方可以感知
2.可靠 。靠机制保障。应答超时重传
八:流式套接字
流式套接字 是一种tcp socket
队列
1.有顺序,连续
2.发送和接收的次数不需要对应。
3.send 发送快,写阻塞
流式套接字 是一种tcp socket
队列
1.有顺序,连续
2.发送和接收的次数不需要对应。
3.send 发送快,写阻塞
流式套接字 是一种tcp socket
队列
1.有顺序,连续
2.发送和接收的次数不需要对应。
3.send 发送快,写阻塞
- 数据之间没有边界
数据没有边界会导致数据的黏包
接收收到数据后,无法正常解析
解决方法1.协商边界
2.固定大小
3.自定义协议
//3.
eg: AA 03 1 2 3 crc BB开始 长度 数据 校验 结束//都是自己定义的形式
九:TCP服务器和客户端函数流程
服务器
socket();
打开网络设备 获得文件描述符(套接字) listfd 监听套接字,作用,就是三次握手
bind();
// 给套接字设定ip(确定主机)+port(对应到进程pid)
listen();
使监听套集字进入监听状态(可以被三次握手的状态)
accept();
// 服务器和客户端进入三次握手阶段,并建立连接。并获得通信套接字(服务器和客户端后续进行通信,用的套接字)
recv() ;
;//阻塞接收客户端的数据。 0 ==ret 代表对方断开连接。-1 ,代表错误。 >0 实际接收到的字节数。
send();
//发送的数据。 发送过程中有可能阻塞。发送的快,把对方的缓冲区填满就阻塞。
close()
.当收到对方的断开请求(0 == recv())。就断开与客户端的通信。
客户端
socket()
; 打开网络设备 获得文件描述符(套接字) ,通信套接字
connect()
;客户端主动连接服务器 。触发三次握手。
send()
;//发送的数据。 发送过程中有可能阻塞。发送的快,把对方的缓冲区填满就阻塞。
recv();
//阻塞接收客户端的数据。 0 ==ret 代表对方断开连接。-1 ,代表错误。 >0 实际接收到的字节数。
close();
当客户端请求服务完成后,主动关闭套接字。触发四次挥手。
9.1:三次握手/四次挥手
三次握手
c->syn(请求连接), c_num(起始发送数据的编号) -> s
s->syn,ACK(应答),S_num(起始发送数据的编号)->c
c -> ACK ->s 四次挥手
c->FIN(断开连接)->ACK-> s
s->ACK (应答上次fin的请求)->c
s-> FIN(断开连接) +ACK (应答上次fin的请求)->c
c -> ACK(s-> FIN(断开连接))->s
十:服务器端
10.1:listen
int listen(int sockfd, int backlog);
功能:在参数1所在的套接字id上监听等待链接。
参数:sockfd
套接字id
backlog
允许链接的个数。
返回值:成功 0, 失败 -1;
10.2:accept
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;
10.3:recv
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;
十一:客户端
11.1:connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数固定有客户端使用,表示从当前主机向目标
主机发起链接请求。
参数:sockfd
本地socket创建的套接子id
addr
远程目标主机的地址信息。
addrlen
: 参数2的长度。
返回值:成功 0, 失败 -1;
ps
:套接字不是单独给网络用的,可以用别名强转
11.2:send
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;
十二:实例
//ser
#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;}struct 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 = INADDR_ANY;//任何人都可以连接,相当于ser.sin_addr.s_addr = 0int 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[512] = {0};int rec_ret= recv(conn, buf, sizeof(buf), 0);if(rec_ret<=0){break;}printf("from cli:%s\n",buf);time_t tm;time(&tm);sprintf(buf, "%s %s", buf, ctime(&tm));int sd_ret = send(conn, buf, strlen(buf), 0);if(sd_ret<=0){break;}}close(listfd);close(conn);// system("pause");return 0;
}
//cli
#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;}struct sockaddr_in ser;bzero(&ser, sizeof(ser));ser.sin_family = AF_INET;ser.sin_port = htons(50000);// 代表本机地址 外部客户端可以连接到服务器ser.sin_addr.s_addr = inet_addr("192.168.116.130");int ret = connect(conn, (SA)&ser, sizeof(ser));if (-1 == ret){perror("connect");return 1;}while (1){char buf[512] = "hello,this tcp test";int sd_ret = send(conn, buf, strlen(buf), 0);if(sd_ret<=0){break;}bzero(buf, sizeof(buf));int ret_rec = recv(conn, buf, sizeof(buf), 0);if(ret_rec<=0){break;}printf("from ser:%s\n",buf);sleep(1);}close(conn);// system("pause");return 0;
}
十三:tcp_cp_struct
//ser
#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 name[256];int size;char buf[4096];int buf_size;} MSG;
int main(int argc, char** argv)
{//监听套接字 功能检测是否有客户端 连连接服务器int listfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == listfd){perror("socket");return 1;}struct 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 = INADDR_ANY;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;}MSG msg;bzero(&msg, sizeof(msg));int flag = 0;int fd = -1;int total_size = 0;int currnet_size = 0;while (1){int re_ret = recv(conn, &msg, sizeof(msg), 0);if (re_ret <= 0){break;}if (0 == flag){flag = 1;fd = open(msg.name, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (-1 == fd){perror("open");return 1;}total_size = msg.size;}if (msg.buf_size <= 0){break;}write(fd, msg.buf, msg.buf_size);currnet_size += msg.buf_size;printf("size:%d\n", currnet_size);if (currnet_size == total_size){ //文件传输结束break;}bzero(&msg, sizeof(msg));strcpy(msg.buf, "go on");send(conn, &msg, sizeof(msg), 0);}close(listfd);close(conn);close(fd);// system("pause");return 0;
}
//cli
#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 <time.h>
#include <unistd.h>
typedef struct sockaddr *(SA);
typedef struct
{char name[256];int size;char buf[4096];int buf_size;} MSG;
int main(int argc, char **argv)
{int conn = socket(AF_INET, SOCK_STREAM, 0);if (-1 == conn){perror("socket");return 1;}struct sockaddr_in ser;bzero(&ser, sizeof(ser));ser.sin_family = AF_INET;ser.sin_port = htons(50000);// 代表本机地址 外部客户端可以连接到服务器ser.sin_addr.s_addr = inet_addr("192.168.116.130");int ret = connect(conn, (SA)&ser, sizeof(ser));if (-1 == ret){perror("connect");return 1;}MSG msg;strcpy(msg.name, "2.png");struct stat st;ret = stat("/home/linux/1.png", &st);if (-1 == ret){perror("stat");return 1;}msg.size = st.st_size;int fd = open("/home/linux/1.png", O_RDONLY);if (-1 == fd){perror("open");return 1;}while (1){msg.buf_size = read(fd, msg.buf, sizeof(msg.buf));send(conn, &msg, sizeof(msg), 0); // 固定大小的方式if (msg.buf_size <= 0){break;}bzero(&msg, sizeof(msg));recv(conn, &msg, sizeof(msg), 0);}close(conn);// system("pause");return 0;
}