六: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;
}