網路編程大雜燴
- 網路編程概念
- 1.位元組序
- 2.位元組序轉換
- 3.網路位元組序---主機位元組序
- 4.地址轉換函式
- UDP編程
- 1.概念
- UDP編程核心代碼介紹
- 發送資料—sendto 函式
- bind函式
- recvform函式介紹
- 2.UDP簡單客戶端代碼演示
- UDP簡單服務器代碼演示
- UDP廣播功能的實作
- UDP多播功能的實作
- TFTP協議介紹
- 1.1TFTP 概述
- .1.2 TFTP 通信程序
- 1.3TFTP 協議分析
- 1.4客戶端檔案下載簡單流程
- 1.5客戶端上傳檔案簡單流程
- 1.6帶選項的TFTP檔案
- TFTP 通信程序總結(帶選項)
- TFTP 的下載程序的代碼演示(不帶選項)
- TFTP 的上傳程序的代碼演示(不帶選項)
- TFTP 的下載程序的代碼演示(帶選項)
- TFTP 的上傳程序的代碼演示(帶選項)
- TCP 網路編程
- socket函式(客戶端和服務端必用)
- bind函式(服務端專屬)
- listen函式(服務端專屬)
- accept 函式(服務端專屬)
- connect 函式(客戶端專屬)
- send函式(客戶端服務端可用)
- recv函式(客戶端服務端可用)
- close 關閉套接字
- 埠復用
- TCP簡單客戶端的撰寫
- TCP簡單服務器的撰寫(只能同時跟一個客戶端交流)
- TCP三次握手
- TCP四次揮手
- 高并發服務器的撰寫(行程版)
- 高并發服務器的撰寫(執行緒版)
網路編程概念
網路通信要解決的是不同主機行程間的通信
1、首要問題是網路間行程標識問題
2、以及多重協議的識別問題,其網路程 序編程開發介面為 socket 隨著 UNIX 以及類 UNIX 作業系統的廣泛應用, socket 成為最流行的網路程式開發介面
socket 作用
提供不同主機上的行程之間的通信
socket 特點
1、socket 也稱“套接字”
2、是一種檔案描述符,代表了一個通信管道的一個端點
3、類似對檔案的操作一樣,可以使用 read、write、close 等函式對 socket 套接字進行網路資料的收取和發 送等操作
4、得到 socket 套接字(描述符)的方法呼叫 socket()
1.位元組序
概念:是指多位元組資料的存盤順序
分類:
1、大端(將高位位元組資料存盤在低地址)
2:小端(將低位位元組資料存盤在低地址)
2.位元組序轉換
主機位元組序轉網路位元組序(網路位元組序默認大端,而主機大部分為小端)
uint32_t htonl(uint32_t hostint32);//
uint16_t htons(uint16_t hostint16);
3.網路位元組序—主機位元組序
uint32_t ntohl(uint32_t netint32);
uint16_t ntohs(uint16_t netint16);
1、網路位元組序一直是大端,且主機位元組序轉網路位元組序就是轉 大端保存,網路位元組序轉主機位元組序就是大端轉大端/小端,
2、只有在多位元組資料處理時才需要考慮位元組序,
3、同一主機間網路行程通信,不需要位元組序轉換,
4、不同主機間網路行程通信,需要位元組序轉換,
4.地址轉換函式
點分十進制數轉整數
int inet_pton(int family,const char *strptr, void *addrptr);
引數
1、協議族AF_INET
2、點分十進制串
3、轉之后的整數
回傳值:1成功
將32位無符號整數轉換成點分十進制數串
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
引數
1、協議族AF_INET
2、32位無符號整數
3、點分十進制串
4、點分十進制串緩沖區的長度(16)
回傳值:成功:則回傳字串的首地址 失敗:回傳 NULL
UDP編程
1.概念
面向無連接的用戶資料報協議,在傳輸資料前不需要先建立連接;目地主機的運輸層收到 UDP 報文后,不需 要給出任何確認
特點:
1、相比 TCP 速度稍快些
2、簡單的請求/應答應用程式可以使用 UDP
3、對于海量資料傳輸不應該使用 UDP
4、廣播和多播應用必須使用 UDP
應用:DNS(域名決議)、NFS(網路檔案系統)、RTP(流媒體)等


- UDP的通信程序
UDP編程核心代碼介紹

發送資料—sendto 函式
發送資料—sendto 函式
ssize_t sendto(int sockfd,const void *buf,
size_t nbytes,int flags,
const struct sockaddr *to,
socklen_t addrlen);
功能: 向 to 結構體指標中指定的 ip,發送 UDP 資料
引數:
sockfd:套接字
buf: 發送資料緩沖區
nbytes: 發送資料緩沖區的大小
flags:一般為 0
to:指向目的主機地址結構體的指標
addrlen:to 所指向內容的長度
注意:
通過 to 和 addrlen 確定目的地址
可以發送 0 長度的 UDP 資料包
回傳值:
bind函式
UDP 網路程式想要收取資料需什么條件?
確定的 ip 地址
確定的 port
怎樣完成上面的條件呢?
接收端 使用 bind 函式,來完成地址結構與 socket 套接字的系結,這樣 ip、port 就固定了
發送端 在 sendto 函式中指定接收端的 ip、port,就可以發送資料了
int bind(int sockfd,
const struct sockaddr *myaddr,socklen_t addrlen);
功能: 將本地協議地址與 sockfd 系結
引數:
sockfd: socket 套接字
myaddr: 指向特定協議的地址結構指標
addrlen:該地址結構的長度
回傳值:
成功:回傳 0
失敗:其他
recvform函式介紹
接收資料—recvfrom 函式
ssize_t recvfrom(int sockfd, void *buf,
size_t nbytes,int flags,
struct sockaddr *from,
socklen_t *addrlen);
功能:
接收 UDP 資料,并將源地址資訊保存在 from 指向的結構中
引數:
sockfd: 套接字
buf:接收資料緩沖區
nbytes:接收資料緩沖區的大小
flags: 套接字標志(常為 0)
from: 源地址結構體指標,用來保存資料的來源
addrlen: from 所指內容的長度
注意:
通過 from 和 addrlen 引數存放資料來源資訊
from 和 addrlen 可以為 NULL, 表示不保存資料來源
回傳值:
成功:接收到的字符數
失敗: -1
2.UDP簡單客戶端代碼演示
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc ,char **argv)
{
//創建一個套接字,本質上是一個檔案描述符
struct sockaddr_in det;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
printf("sockfd = %d\n", sockfd);
//指定發送給誰,系結目的主機
det.sin_family = AF_INET;
det.sin_port = htons(8888);
inet_pton(AF_INET, argv[1],&det.sin_addr);
memset(det.sin_zero, 0, 8);
printf("%u,%u\n", det.sin_addr, det.sin_port);
// int err = bind(sockfd, (struct sockadd *)&det, sizeof(det));
// if(err < 0)
// {
// printf("bind error\n");
// }
while(1)
{
int fd = fork();
if(fd == 0)
{
char buf[50];
socklen_t form_S = sizeof(det);
recvfrom(sockfd, buf, sizeof(buf),0,(struct sockaddr*)&det,&form_S);
printf("from server:%s\n",buf);
}
else
{
char *buf = (char *)malloc(20);
fgets(buf, 20, stdin);
*(buf + strlen(buf) - 1) = '\0';
int a = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&det, sizeof(det));
printf("send num:%d\n", a);
if (strcmp(buf, "bye") == 0)
{
break;
}
}
}
}
UDP簡單服務器代碼演示
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc ,char **argv)
{
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
int pipe_fd[2];
pipe(pipe_fd);
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(10086);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int err = bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if(err == -1)
{
printf("bind error\n");
}
while(1)
{
int fd = fork();
if(fd ==0)
{
char buf[20];
unsigned int client_ip;
char from_ip[16]=" ";
struct sockaddr_in client_addr;
socklen_t addrlen=sizeof(client_addr);
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(10086);
read(pipe_fd[0], (void *)&client_addr.sin_addr, sizeof( client_addr.sin_addr));
// inet_pton(AF_INET, "10.36.145.41",&client_addr.sin_addr);
printf("recevie :%s\n",from_ip);
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
int err = sendto(sock_fd, buf, strlen(buf), 0,(struct sockaddr *)&client_addr, addrlen);
printf("發送的資料%d\n", err);
}
}
else
{
char buf[20];
char from_ip[16]=" ";
struct sockaddr_in from_addr;
bzero(&from_addr,sizeof(from_addr));
memset(buf, 0, sizeof(buf));
memset(from_ip, 0, sizeof(from_ip));
socklen_t addrlen=sizeof(from_addr);
//接收到源ip
recvfrom(sock_fd, buf, 20, 0, (struct sockaddr *)&from_addr,&addrlen);
write(pipe_fd[1], (void *)&from_addr.sin_addr.s_addr, sizeof(from_addr.sin_addr.s_addr));
//決議源ip的地址
inet_ntop(AF_INET, (const void *)&from_addr.sin_addr.s_addr, from_ip,(socklen_t)16);
printf("receive from :%s\n", from_ip);
printf("buf :%s\n", buf);
// sendto(sock_fd, buf, sizeof(buf), 0, (struct sockaddr *)&from_addr, addrlen);
}
}
}
UDP廣播功能的實作
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
/*這是一個廣播的程式,如何向局域網進行全體主機的廣播,僅限于用在局域網
,城域網和廣域網不適用,因為他們太多主機ip了
*/
#if 0
int main(int argc,char**argv)
{
struct sockaddr_in det;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
printf("sockfd = %d\n", sockfd);
//更改mac地址為全f,修改套接字允許發送廣播的訊息
int yes = 1;
setsockopt(sockfd, SOL_SOCKET,SO_BROADCAST, &yes, sizeof(yes));
//指定廣播
det.sin_family = AF_INET;
det.sin_port = htons(8080);
socklen_t addrlen = sizeof(det);
inet_pton(AF_INET, argv[1],&det.sin_addr);
memset(det.sin_zero, 0, 8);
printf("%u,%u\n", det.sin_addr.s_addr, det.sin_port);
while(1)
{
char buf[64] = " ";
fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf,sizeof(buf), 0, (const struct sockaddr *)&det, addrlen);
}
}
#endif
UDP多播功能的實作
/*
這是一個udp多播的最基本的演示的程式
*/
int main(int argc,char**argv)
{
struct sockaddr_in det;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
printf("sockfd = %d\n", sockfd);
char group[INET_ADDRSTRLEN] = "224.0.1.1";
//定義一個多播地址
struct ip_mreq mrep;
//添加一個多播組IP
mrep.imr_multiaddr.s_addr = inet_addr(group);
//添加一個將要添加到多播組的ip
mrep.imr_interface.s_addr = htons(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mrep, sizeof(mrep));
//賦值
det.sin_family = AF_INET;
det.sin_port = htons(8080);
/*轉換過來就是0.0.0.0,泛指本機的意思
,也就是表示本機的所有IP,因為有些機子不止一塊網卡,多網卡的情況下,
這個就表示所有網卡ip地址的意思,
比如一臺電腦有3塊網卡,分別連接三個網路,
那么這臺電腦就有3個ip地址了,如果某個應用程式需要監聽某個埠,那他要監聽哪個網卡地址的埠呢?*/
det.sin_addr.s_addr = htons(INADDR_ANY);//添加自己本地的ip地址
socklen_t addrlen = sizeof(det);
memset(det.sin_zero, 0, 8);
//系結
bind(sockfd, (const struct sockaddr *)&det, addrlen);
while(1)
{
char buf[64] = "";
recvfrom(sockfd, buf, sizeof(buf), 0, NULL,NULL);
printf("rece:%s\n", buf);
}
}
/*
多播實驗總結
1.要先把當前ip加入到多播組的地址,然后客戶端發送訊息的時候會發送給 224.0.1.1,這個時候
會把訊息發送給所有加入到多播組ip,如果不想了還可以把ip地址移出多播組
*/
TFTP協議介紹
1.1TFTP 概述
TFTP:簡單檔案傳送協議
最初用于引導無盤系統,被設計用來傳輸小檔案
特點:
基于 UDP 協議實作
不進行用戶有效性認證
資料傳輸模式:
octet:二進制模式
netascii:文本模式
mail:已經不再支持
.1.2 TFTP 通信程序

- .服務器從固定埠69接收客戶端得讀寫請求
- 接收到請求后服務器使用一個臨時埠發送資料包給客戶端,固定為512個位元組,也可以改固定位元組的長度
- 客戶端接收到資料后發送一個ack說我已經收到了
- 如果服務器發送的資料包小于512個位元組,那么代表服務器發送資料已經發完了,結束傳輸
1.3TFTP 協議分析

1.4客戶端檔案下載簡單流程
- 創建udp套接字
- 組讀請求的資料包(0,1,“檔案名”,0,“模式”,0),發送給服務器
- 回圈接收資料,資料包buf長度<516,退出,否則繼續
- 查看資料包buf[1],如果buf[1] == 3 ;寫檔案,回復ack;buf[1] == 5;break;
1.5客戶端上傳檔案簡單流程
- 創建套接字
- 組寫請求的資料包(0,1,“檔案名”,0,“模式”,0)
- 客戶端打開要上傳的檔案
- 回圈發送資料buf,如果讀本地檔案長度<512,break;
1.6帶選項的TFTP檔案
tsize 選項
當讀操作時,tsize 選項的引數必須為“0”,服務器會回傳待讀取的檔案的大小
當寫操作時,tsize 選項引數應為待寫入檔案的大小,服務器會回顯該選項
blksize 選項
修改傳輸檔案時使用的資料塊的大小(范圍:8~65464)
timeout 選項
修改默認的資料傳輸超時時間(單位:秒)
TFTP 通信程序總結(帶選項)
1、可以通過發送帶選項的讀/寫請求發送給 server
2、如果 server 允許修改選項則發送選項修改確認包
3、server 發送的資料、選項修改確認包都是臨時 port
4、server 通過 timeout 來對丟失資料包的重新發送
TFTP 的下載程序的代碼演示(不帶選項)
我們來演示一下如何從服務器下載自己想要的檔案存進里面
實作的是TFTP的客戶端下載與上傳功能,
TFTP軟體下載鏈接
我們要和這個軟體配套做實驗
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[]){
//創建套接字
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
printf("創建失敗\n");
return 0;
}
else{
printf("創建成功 %d\n",sockfd);
}
//給服務器發送下載請求
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=htons(69);
inet_pton(AF_INET,"10.36.145.220",(void *)&ser_addr.sin_addr);
char buf[64]="";
int buf_len=sprintf(buf,"%c%c%s%c%s%c",0,1,"a.txt",0,"netascii",0);
sendto(sockfd,buf,buf_len,0,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
//打開本地檔案用來接收服務器資料
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd<0){
perror("open");
return 0;
}
//接收資料包
char rcv_buf[1024]="";
int rcv_buf_len=0;
struct sockaddr_in from_addr;
socklen_t addrlen=sizeof(from_addr);
while(1){
//接收資料函式
bzero(&from_addr,sizeof(from_addr));
rcv_buf_len= recvfrom(sockfd, rcv_buf, sizeof(rcv_buf),0,(struct sockaddr*)&from_addr,&addrlen);
if(rcv_buf[1]==5)//差錯
{
printf("error:%d-%d%s\n",rcv_buf[2],rcv_buf[3],rcv_buf+4);
break;
}
else if(rcv_buf[1]==3)//資料包
{
//寫檔案
write(fd,rcv_buf+4,rcv_buf_len-4);
//回復ACK
rcv_buf[1]=4;
sendto(sockfd,rcv_buf,4,0,(struct sockaddr *)&from_addr,sizeof(from_addr));
if(rcv_buf_len<516)
break;
}
else;
}
close(sockfd);
close(fd);
return 0;
}
TFTP 的上傳程序的代碼演示(不帶選項)
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[]){
//創建套接字
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
printf("創建失敗\n");
return 0;
}
else{
printf("創建成功 %d\n",sockfd);
}
//給服務器發送上傳請求
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=htons(69);
inet_pton(AF_INET,"10.36.145.220",(void *)&ser_addr.sin_addr);
char buf[64]="";
int buf_len=sprintf(buf,"%c%c%s%c%s%c",0,2,"a.txt",0,"netascii",0);
sendto(sockfd,buf,buf_len,0,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
//打開本地檔案
int fd = open("a.txt",O_RDWR);
if(fd<0){
perror("open");
return 0;
}
//接收資料包
char rcv_buf[1024]="";
int rcv_buf_len=0;
unsigned short p_num=0;
struct sockaddr_in from_addr;//用來保存服務器ip和服務器的臨時埠
socklen_t addrlen=sizeof(from_addr);
while(1){
//接收資料函式
bzero(&from_addr,sizeof(from_addr));
recvfrom(sockfd, rcv_buf, sizeof(rcv_buf),0,(struct sockaddr*)&from_addr,&addrlen);
if(rcv_buf[1]==5)//差錯
{
printf("error:%s\n",rcv_buf+4);
break;
}
else if(rcv_buf[1]==4)//ACK 0 4 0 0-------0 3 0 1 512byte
{
//讀本地檔案
rcv_buf_len= read(fd,rcv_buf+4,512);// 0 4 0 0-------0 4 0 0 512byte
rcv_buf[1]=3;// 0 4 0 0-------0 3 0 0 512byte
p_num=ntohs(*(unsigned short *)(rcv_buf+2));//取rcv_buf的3~4位元組并保存到主機
*(unsigned short *)(rcv_buf+2)=htons(p_num+1);
//將ack包的編號+1,賦值給資料包0 4 0 x-------0 3 0 x+1 512byte
printf("即將發送資料包編號:%d\n",p_num+1);
sendto(sockfd,rcv_buf,rcv_buf_len+4,0,(struct sockaddr *)&from_addr,sizeof(from_addr));
if(rcv_buf_len<512)
break;
}
else;
}
close(sockfd);
close(fd);
return 0;
}
TFTP 的下載程序的代碼演示(帶選項)
帶選項的好處是可以修改每次發送的資料包的大小,ack的超時時間或者是其他的資訊,
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[]){
//創建套接字
int sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
printf("創建失敗\n");
return 0;
}
else{
printf("創建成功 %d\n",sockfd);
}
//給服務器發送下載請求
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=htons(69);
inet_pton(AF_INET,"10.36.145.220",(void *)&ser_addr.sin_addr);
char buf[64]="";
int buf_len=sprintf(buf,"%c%c%s%c%s%c%s%c%s%c",0,1,"a.txt",0,"netascii",0,"blksize",0,"1024",0);//發送帶選項請求
sendto(sockfd,buf,buf_len,0,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
//打開本地檔案用來接收服務器資料
int fd = open("a.txt",O_RDWR|O_CREAT,0666);
if(fd<0){
perror("open");
return 0;
}
//接收資料包
char rcv_buf[1056]="";
int rcv_buf_len=0;
int len=0;
unsigned short p_num=0;
struct sockaddr_in from_addr;
socklen_t addrlen=sizeof(from_addr);
//接受OACK
recvfrom(sockfd, rcv_buf, sizeof(rcv_buf),0,(struct sockaddr*)&from_addr,&addrlen);
if(rcv_buf[1]==6)//0 6 選項 0 值 0
{
rcv_buf[1]=4;//0 4 選項 0 值 0
rcv_buf[2]=0;//0 4 0 0 值 0
rcv_buf[3]=0;//0 4 0 0 值 0
sendto(sockfd,rcv_buf,4,0,(struct sockaddr *)&from_addr,sizeof(from_addr));
}
else{
return 0;
}
while(1){
//接收資料函式
bzero(&from_addr,sizeof(from_addr));
rcv_buf_len= recvfrom(sockfd, rcv_buf, sizeof(rcv_buf),0,(struct sockaddr*)&from_addr,&addrlen);
if(rcv_buf[1]==5)//差錯
{
printf("error:%s\n",rcv_buf+4);
break;
}
else if(rcv_buf[1]==3)//資料包
{
//寫檔案
len=write(fd,rcv_buf+4,rcv_buf_len-4);
//列印資料包編號
p_num=ntohs(*(unsigned short * )(rcv_buf+2));
printf("資料包編號:%d --- %d\n",p_num,len);
//回復ACK
rcv_buf[1]=4;
sendto(sockfd,rcv_buf,4,0,(struct sockaddr *)&from_addr,sizeof(from_addr));
if(rcv_buf_len<1028)
break;
}
else;
}
close(sockfd);
close(fd);
return 0;
}
TFTP 的上傳程序的代碼演示(帶選項)
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
//客戶端有選項的上傳
#if 1
int main(int argc,char**argv)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
int p_num;
char buf[64] ;
char rece_buf[1056] =" ";
printf("sockfd = %d\n", sockfd);
struct sockaddr_in det;
struct sockaddr_in server;
//判斷一下 執行的時候是否攜帶了引數
if(argc<2)
{
printf("你還沒有指定服務器的ip地址\n");
return 0 ;
}
//指定發送給誰,系結目的主機
bzero(&det,sizeof(det));
det.sin_family = AF_INET;
det.sin_port = htons(69);
inet_pton(AF_INET, argv[1],&det.sin_addr);
memset(det.sin_zero, 0, 8);
printf("%u,%u\n", det.sin_addr.s_addr, det.sin_port);
socklen_t addrlen = sizeof(det);
//創建一個接收從服務器下載的檔案
//組裝資料包并進行發送
int buflen = sprintf(buf,"%c%c%s%c%s%c%s%c%s%c",0,2,"up.txt",0,"netascii",0,"blksize",0,"1024",0);//資料包的大小要對,不能傳多了
int sendnum = sendto(sockfd, buf,buflen, 0, (const struct sockaddr *)&det, addrlen);
printf("send num %d \n",sendnum);
int fd = open("a.txt", O_RDWR | O_CREAT, 0666);
if(fd<1)
{
printf("open error \n");
return 0 ;
}
//等待服務器osak的應答
char oask_buf[1056]="";
int rece_osk_num = recvfrom(sockfd, oask_buf, sizeof(oask_buf), 0, ( struct sockaddr *)&server, &addrlen);
if(oask_buf[1]!=6)
{
printf("應答失敗\n");
return 0;
}
while(1)
{
//讀取資料
int recebuf_len = read(fd, rece_buf + 4, 1028);
//把操作碼變成3,表示向服務器發送應答命令 0 0 0 0
rece_buf[1] = 3;
p_num = ntohs(*(unsigned short *)(rece_buf + 2));
*(unsigned short *)(rece_buf + 2) = htons(p_num + 1);
printf("即將發送的編號:%d\n", p_num + 1);
sendto(sockfd, rece_buf,recebuf_len+4, 0,(const struct sockaddr *)&server, addrlen);//發送的時候要使用目的ip和源埠,因為使用的是臨時埠,所以要小心
//判斷接收的位元組是否小于1028,如果小于則結束
if(recebuf_len<1028)
{
printf("檔案傳輸完成\n");
break;
}
// memset(oask_buf, 0, sizeof(oask_buf));
// recvfrom(sockfd, oask_buf, sizeof(oask_buf), 0, (struct sockaddr *)&server, &addrlen);
// if (oask_buf[1] == 5)
// {
// printf("error :%s\n", oask_buf + 4);
// }
// else if(oask_buf[1] == 4)
// {
// printf("服務器應答\n");
// }
}
close(sockfd);
close(fd);
}
#endif
TCP 網路編程
TCP特點:
1、面向連接的流式協議;可靠、出錯重傳、且每收到一個資料都要給出相應的確認
2、通信之前需要建立鏈接
3、服務器被動鏈接,客戶端是主動鏈接
TCP客戶端與服務端流程
socket函式(客戶端和服務端必用)
iint socket(int family,int type,int protocol);
功能:創建一個用于網路通信的 socket 套接字(描述符)
引數:
family:協議族(AF_INET、AF_INET6、PF_PACKET 等),通常使用AF_INET,針對internet的
type:套接字類(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW 等)
SOCK_STREAM:代表TCP協議
SOCK_DGRAM :表示UDP協議
protocol:協議類別(0、IPPROTO_TCP、IPPROTO_UDP 等 ,通常使用0來代替
回傳值:成功回傳檔案描述符,失敗回傳-1
特點:創建套接字時,系統不會分配埠 創建的套接字默認屬性是主動的,即主動發起服務的請求;當作為服務器時,往往需要修改為被動的
頭檔案:#include <sys/socket.h>
bind函式(服務端專屬)
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
功能:從函式用于將地址系結到一個套接字,
引數:
sockfd是由socket函式呼叫回傳的檔案描述符,
my_addr是一個指向sockaddr的指標,
addrlen是sockaddr結構的長度,
回傳值:失敗回傳-1
sockaddr的定義:
當呼叫編程介面函式,且該函式需要傳入地址結構時需要用 struct sockaddr 進行強制轉換
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
sockaddr_in的定義:
在定義源地址和目的地址結構的時候,選用 struct sockaddr_in;
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
引數:
Internet所以sin_family一般為AF_INET,
sin_port:監聽的埠號
sin_addr:通常設定為INADDR_ANY表示可以和任何的主機通信
sin_zero[8]:通常不理它
listen函式(服務端專屬)
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:將套接字由主動修改為被動 使作業系統為該套接字設定一個連接佇列,用來記錄所有連接到該套接字的連接
引數:
sockfd: socket 監聽套接字
backlog:連接佇列的長度
回傳值:成功回傳 0 失敗回傳-1
accept 函式(服務端專屬)
int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);
功能:從已連接佇列中取出一個已經建立的連接,如果沒有任何連接可用,則進入睡眠等待(阻塞)
引數:
sockfd: socket 監聽套接字
cliaddr: 用于存放客戶端套接字地址結構,用來給客戶端程式自動填寫,服務器端只用傳遞指標就行,
addrlen:套接字地址結構體長度的地址
回傳值:已連接套接字
頭檔案:#include <sys/socket.h>
注意:回傳的是一個已連接套接字,這個套接字代表當前這個連接
connect 函式(客戶端專屬)
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
功能:主動跟服務器建立鏈接
引數:
sockfd:socket 套接字
addr: 連接的服務器地址結構
len: 地址結構體長度
回傳值:成功:0 失敗:其他
注意:
1、connect 建立連接之后不會產生新的套接字
2、連接成功后才可以開始傳輸 TCP 資料
3、頭檔案:#include <sys/socket.h>
send函式(客戶端服務端可用)
ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
功能:用于發送資料
引數:
sockfd: 已建立連接的套接字
buf: 發送資料的地址
nbytes: 發送緩資料的大小(以位元組為單位)
flags: 套接字標志(常為 0)
回傳值:成功發送的位元組數
頭檔案:#include <sys/socket.h> 注意:不能用 TCP 協議發送 0 長度的資料包
recv函式(客戶端服務端可用)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd 指定接收端套接字描述符;
引數:
buf 指明一個緩沖區,該緩沖區用來存放recv函式接收到的資料;
len 指明buf的長度;
flags 一般置0,
客戶或者服務器應用程式都用recv函式從TCP連接的另一端接收資料,
close 關閉套接字
1、使用 close 函式即可關閉套接字 關閉一個代表已連接套接字將導致另一端接收到一個 0 長度的資料包
2、做服務器時關閉監聽套接字將導致服務器無法接收新的連接,但不會影響已經建立的連接,關閉 accept 回傳的已連接套接字將導致它所代表的連接被關閉,但不會影響服務器的監聽
3、做客戶端時 關閉連接就是關閉連接,不意味著其他
埠復用
- 當出現連接失敗,或者地址被占用等錯誤,可以設定埠復用
- 一個程式多個任務系結同一個埠
- 一個任務系結多個埠
- UDP多播也可以設定埠復用
//設定埠復用
int yes=1;
setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR,&yes, sizeof(yes));
//設定套接字支持廣播
int yes=1;
setsockopt(sockfd, SOL_SOCKET,SO_BROADCAST,&yes, sizeof(yes));
//設定多播
setsockopt(sockfd, IPPROTO_IP,IP_ADD_MEMBERSHIP, &merq, sizeof(merq));
TCP簡單客戶端的撰寫
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <pthread.h>
int main(int argc,char **argv)
{
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
pthread_t pth;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
socklen_t addrlen = sizeof(server_addr);
inet_pton(AF_INET, "10.36.145.33", &server_addr.sin_addr.s_addr);
int err = connect(sock_fd, (struct sockaddr *)&server_addr, addrlen);
// printf("%u\n", INADDR_ANY);
// char ip[48];
// //
// // send(sock_fd, ip, strlen(ip), 0);
// inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, ip, 16);
// printf("%s\n", ip);
if (err != 0)
{
perror("socket");
}
while (1)
{
char buf[63];
fgets(buf, sizeof(63), stdin);
send(sock_fd, buf, strlen(buf), 0);
}
}
TCP簡單服務器的撰寫(只能同時跟一個客戶端交流)
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <pthread.h>
#if 1
/*
功能:只能同時和一個客戶端通信,除非客戶端退出或者發送bye的指令才能連接下一個
*/
#define client_max 10
struct sockaddr_in cli_addr;
int sock_fd;
int sockfd_new;
int state = 1;
struct sockaddr_in my_addr;
socklen_t addrlen;
int main()
{
// 1.創建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in my_addr;
pthread_t pth;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(6666);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
addrlen = sizeof(my_addr);
//2.服務器系結
int err=bind(sock_fd, (struct sockaddr *)&my_addr, addrlen);
if(err == -1)
{
perror("socket");
}
//3.監聽套接字,由主動變成被動
err = listen(sock_fd, client_max);
if(err == -1)
{
perror("socket");
}
printf("success\n");
while (1)
{
char ip[16] = "";
if(state==1)
{
// bzero(&cli_addr, addrlen);
//4.等待客戶端連接,否則阻塞
sockfd_new = accept(sock_fd, (struct sockaddr *)&cli_addr, &addrlen);
inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, addrlen);
printf("%d:%s 已連接\n", sockfd_new, ip);
}
state = 2;
char buf[64]="";
recv(sockfd_new, buf, sizeof(buf), 0);
printf("%d\n", sockfd_new);
if(strncmp(buf,"bye",3)== 0||strlen(buf)== 0)
{
printf("對方已經關閉\n");
state = 1;
break;
}
}
return 0;
}
#endif
TCP三次握手

1.源埠號:發送方埠號
2.目的埠號:接收方埠號
3.序列號:本報文段的資料的第一個位元組的序號
4.確認序號:期望收到對方下一個報文段的第一個資料位元組的序號
5.首部長度(資料偏移):TCP報文段的資料起始處距離TCP報文段的起始處有多遠,即首部長度,單 位:32位,即以4位元組為計算單位,
6.保留:占6位,保留為今后使用,目前應置為0
7.緊急URG: 此位置1,表明緊急指標欄位有效,它告訴系統此報文段中有緊急資料,應盡快傳送
8.確認ACK: 僅當ACK=1時確認號欄位才有效,TCP規定,在連接建立后所有傳達的報文段都必須把ACK 置1
9.推送PSH:當兩個應用行程進行互動式的通信時,有時在一端的應用行程希望在鍵入一個命令后立即 就能夠收到對方的回應,在這種情況下,TCP就可以使用推送(push)操作,這時,發送方TCP把PSH 置1,并立即創建一個報文段發送出去,接收方收到PSH=1的報文段,就盡快地(即“推送”向前)交 付給接收應用行程,而不再等到整個快取都填滿后再向上交付
10.復位RST: 用于復位相應的TCP連接
11.同步SYN: 僅在三次握手建立TCP連接時有效,當SYN=1而ACK=0時,表明這是一個連接請求報文段, 對方若同意建立連接,則應在相應的報文段中使用SYN=1和ACK=1.因此,SYN置1就表示這是一個連接請 求或連接接受報文
12.終止FIN:用來釋放一個連接,當FIN=1時,表明此報文段的發送方的資料已經發送完畢,并要求釋 放運輸連接,
13.視窗:指發送本報文段的一方的接收視窗(而不是自己的發送視窗)
14.校驗和:校驗和欄位檢驗的范圍包括首部和資料兩部分,在計算校驗和時需要加上12位元組的偽頭部
15.緊急指標:僅在URG=1時才有意義,它指出本報文段中的緊急資料的位元組數(緊急資料結束后就是 普通資料),即指出了緊急資料的末尾在報文中的位置,注意:即使視窗為零時也可發送緊急資料
16.選項:長度可變,最長可達40位元組,當沒有使用選項時,TCP首部長度是20位元組


第一次連接:當客戶端呼叫connect函式時,客戶端一個SYN包給服務器給服務器:序列號 =0,確認序號 = 0

第二次連接:服務器接收到syn包時,第一次SYN給客戶端:序列號 0 ,確認序號為1

第三次連接:客戶端表示收到了并且發一個ack給服務器 :序列號0,確認序號 =1

TCP四次揮手
我們回顧一下TCP報頭:

四次揮手的條件:呼叫close函式

我們由圖可知道:
第一次揮手:序列號= m 確認號 = m+1

第二次揮手:序列號=m+1 確認號 = m+1,這個時候回復一個ACK為1

第三次揮手:序列號=m+1 確認號 = m+1,再次發一個FIN的報文

第四次揮手: 序列號 = m+1 確認號 = m+2 ,最后發送一個ACK應答報文

總結:假設是客戶端斷開了連接,先發送一個FIN,服務器回復一個ACK應答,發送完應答后再次發送一個FIN,最后客戶端發送一個ACK確認斷開連接,
問題:為什么斷開連接和ack不能在一個資料包中發送
答:因為只有呼叫close函式才會發送斷開連接資料包,但是ack只要收到對方斷開連接資料包就會回復,ack和fin之間存在時間差,所以不可以同時發送ack和fin,就會產生一端連續發兩次給另一端
問題:主動斷開連接的是客戶端,當收到服務器發送到的ack后,還能在接收資料,
答:因為還沒有揮手完,這個時候客戶端處于半關閉狀態,可以接收資料,
高并發服務器的撰寫(行程版)
概念:以前咱們撰寫的是一個服務器只能同時連接一個客戶端,高并發的服務器是可以同時連接多個客戶端并進行通信,
#if 1
/*
功能:一個服務器同時和多個客戶端通信(使用行程的方法)
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <pthread.h>
#define client_max 10
struct sockaddr_in cli_addr;
int sock_fd;
int sockfd_new;
int state = 1;
int pid;
struct sockaddr_in my_addr;
socklen_t addrlen;
void *pthread (void *arg)
{
}
int main()
{
// 1.創建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in my_addr;
pthread_t pth;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(6666);//這個埠要和客戶端的埠一樣
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
addrlen = sizeof(my_addr);
//設定埠復用
int yes = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
//2.服務器系結
int err = bind(sock_fd, (struct sockaddr *)&my_addr, addrlen);
if (err == -1)
{
perror("socket");
}
//3.監聽套接字,由主動變成被動
err = listen(sock_fd, client_max);
if (err == -1)
{
perror("socket");
}
printf("success\n");
while (1)
{
// bzero(&cli_addr, addrlen);
//4.等待客戶端連接,否則阻塞
sockfd_new = accept(sock_fd, (struct sockaddr *)&cli_addr, &addrlen);
pid = fork();
if (pid < 0)
{
printf("fork error\n");
}
else if (pid == 0)
{
close(sock_fd);//這個套接字用不到
char ip[16] = "";
inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, addrlen);
printf("已連接%s\n", ip);
while (1)
{
char buf[64] = "";
recv(sockfd_new, buf, sizeof(buf), 0);
printf("%d\n", sockfd_new);
if (strncmp(buf, "bye", 3) == 0 || strlen(buf) == 0)
{
printf("對方已經關閉\n");
close(sockfd_new);
break;
}
}
exit(1);
}
else if (pid > 0)
{
close(sockfd_new);//父行程不需要這個,子行程內才需要
}
}
return 0;
}
#endif
高并發服務器的撰寫(執行緒版)
概念:以前咱們撰寫的是一個服務器只能同時連接一個客戶端,高并發的服務器是可以同時連接多個客戶端并進行通信,
#if 1
/*
功能:一個服務器同時和多個客戶端通信(使用執行緒的方法)
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <strings.h>
#include <pthread.h>
#define client_max 10
int sock_fd;
int pid;
typedef struct
{
int sockfd_new;
char ip[16];
struct sockaddr_in cli_addr;
} Myadd;
struct sockaddr_in my_addr;
socklen_t addrlen;
void *pthread (void *arg)
{
Myadd p = *(Myadd *)arg;
inet_ntop(AF_INET, &p.cli_addr.sin_addr.s_addr, p.ip, 16);
printf("%d,%s已連接\n",p.sockfd_new, p.ip);
while (1)
{
printf("%p\n", &p);
char buf[64] = "";
recv(p.sockfd_new, buf, sizeof(buf), 0);
printf("%d,%s", p.sockfd_new, buf);
if (strncmp(buf, "bye", 3) == 0 || strlen(buf) == 0)
{
printf("對方已經關閉\n");
close(p.sockfd_new);
break;
}
}
}
int main()
{
// 1.創建套接字
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in my_addr;
pthread_t pth;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(6666);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
addrlen = sizeof(my_addr);
//設定埠復用
int yes = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
//2.服務器系結
int err = bind(sock_fd, (struct sockaddr *)&my_addr, addrlen);
if (err == -1)
{
perror("socket");
}
//3.監聽套接字,由主動變成被動
err = listen(sock_fd, client_max);
if (err == -1)
{
perror("socket");
}
printf("success\n");
while (1)
{
Myadd add;
bzero(&add.cli_addr, addrlen);
//4.等待客戶端連接,否則阻塞
add.sockfd_new = accept(sock_fd, (struct sockaddr *)&add.cli_addr, &addrlen);
pthread_create(&pth, NULL, pthread, (void*)&add);//創建執行緒
pthread_detach(pth);
usleep(1000);
}
return 0;
}
#endif
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/303285.html
標籤:其他
上一篇:解決Cipher Suites導致的“未能創建 SSL/TLS 安全通道”例外問題
下一篇:(42)java Spring Cloud+Spring boot+mybatis企業快速開發架構之SpringCloud-Gateway過濾器工廠的使用


