Socket的由來:
Socket 是一組具體的 UNIX 系統呼叫 是 80 年代美國的研究單位 研究在UNIX中接收 TCP/IP 軟體的問題 並使其亦適應於其他場合 所以研究者們創造一種新的通訊介面
盡可能使用現有的UNIX系統呼叫
然後為支援那些不易被整合於現有函式庫的
TCP/IP 函數 新定義了一些系統呼叫函數 這便是承接口介面(Socket Interface)至今已被廣泛的認可與應用,
成為一種標準了
什麼是TCP/IP:
TCP/IP(Transmission Control
Protcol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的
各個層介紹:
l
應用層 (Application Layer)
各種網路應用如 telnet、Ftp、WWW、Email、News、BBS等。
l
傳輸層 (Transport Layer)
負責網路連接之建立、分割/組合傳送訊息、提供使用節點間資料流量的控制、決定提供網路資料傳送的服務品質等。提供可靠、有效率的連接給網路應用節點使用。
l
網路層 (IP Network Layer)
承接傳輸層傳送的封包,依所欲傳送的位址自動Route轉送路徑、轉換不同協定的封包格式、監控網路流量狀況,動態建構網路整體拓樸架構,動態提供最佳的Route資料傳送路徑。Internet網路層協定為 IP(Internet Protocol)。
l
鏈接層 (Network Access
Layer)
承接網路層傳送的封包,做更細的資料框(Frame)的切割/組合、檢出/更正錯誤的傳送資料、運用ACK 判斷資料的正常傳送與控制傳送速度,及負責傳送由0與 1組成的原始網路資料。
協議的關係圖:
Socket是什麼呢?
Socket是應用層與TCP/IP協議通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議隱藏在Socket介面後面,對用戶來說,一組簡單的介面就是全部,讓Socket去組織數據,以符合指定的協議。
Socket在哪裡?
Socket的建立:
為了建立Socket,程式可以調用Socket函數,該函數返回一個類似文件描述符的控制碼。
socket 函數原型為:
int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,Socket介面還定義了原始Socket(SOCK_RAW),允許程式使用低層協議;protocol通常賦值"0"。Socket()調用返回一個整型socket描述符,你可以在面的調用使用它。
Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。Socket執行體為你管理描述符表。
兩個網絡程式之間的一個網絡連接包括五種資訊:通信協議、本地協議位址、本地主機埠、遠端主機位址和遠端協議埠。Socket數據結構中包含這五種資訊。
Socket的配置:
通過socket調用返回一個socket描述符,在使用socket進行網絡傳輸以前,必須配置該socket。
面向連接的socket客戶端通過調用Connect函數在socket數據結構中保存本地和遠端資訊。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用bind函數來配置本地資訊。
Bind函數將socket與本機上的一個埠相關聯,隨你就可以在該埠監聽服務請求。Bind函數原型為:
int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
Sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP位址及埠號等資訊的sockaddr類型的指針;addrlen常被設置為sizeof(struct sockaddr)。
struct sockaddr結構類型是用來保存socket資訊的:
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
sa_family一般為AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP位址和埠號。
另外還有一種結構類型:
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 埠號 */
struct in_addr sin_addr; /* IP地址 */
unsigned char sin_zero[8]; /* 填充0 以保持與struct sockaddr同樣大小 */
};
這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()或memset()函數將其置為零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味著如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向 sockaddr_in的指針轉換為指向sockaddr的指針;或者相反。
使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被佔用的埠號:
my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的埠號 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置為0,函數會自動為你選擇一個未佔用的埠來使用。同樣,通過將my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數是需要將sin_port和sin_addr轉換成為網絡字節優先順序;而sin_addr則不需要轉換。
計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對在內部是以低位元字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
下麵是幾個字節順序轉換函數:
﹒htonl():把32位值從主機字節序轉換成網絡字節序
﹒htons():把16位值從主機字節序轉換成網絡字節序
﹒ntohl():把32位值從網絡字節序轉換成主機字節序
﹒ntohs():把16位值從網絡字節序轉換成主機字節序
Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置為相應的錯誤號。需要注意的是,在調用bind函數時一般不要將埠號置為小1024的值,因為1到1024是保留埠號,你可以選擇大1024中的任何一個沒有被佔用的埠號。
連結建立:
面向連接的客戶程式使用Connect函數來配置socket並與遠端服務器建立一個TCP連接,其函數原型為:
int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd是socket函數返回的socket描述符;serv_addr是包含遠端主機IP位址和埠號的指針;addrlen是遠端地質結構的長度。Connect函數在出現錯誤時返回-1,並且設置errno為相應的錯誤碼。進行客戶端程式設計無須調用bind(),因為這種情況下只需知道目的機器的IP位址,而客戶通過哪個埠與服務器建立 連接並不需要關心,socket執行體為你的程式自動選擇一個未被佔用的埠,並通知你的程式數據什時候到打斷口。
Connect函數啟動和遠端主機的直接連接。只有面向連接的客戶程式使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啟動一個連接,它只是被動的在
協議埠監聽客戶的請求。
Listen函數使socket處被動的監聽模式,並為該socket建立一個輸入數據隊列,將到達的服務請求
保存在此隊列中,直到程式處理它們。
保存在此隊列中,直到程式處理它們。
int listen(int sockfd, int backlog);
Sockfd是Socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連 接請求將在隊列中等待accept()它們(參考下文)。Backlog對隊列中等待服務的請求的數目進行了限制, 大多數系統缺省值為20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯資訊。當出現錯誤時listen函數返回-1,並置相應的errno錯誤碼。
accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列,服務器就調用accept函數,然後睡眠並等待客戶的連接請求。
int accept(int sockfd, void *addr, int *addrlen);
sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的資訊(某台主機從某個埠發出該請求);addrten通常為一個指向值為
sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。
sizeof(struct sockaddr_in)的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。
首先,當accept函數監視的socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯系起來,收到服務請求的初始socket仍可以繼續在以前的 socket上監聽,
同時可以在新的socket描述符上進行數據傳輸操作。
數據傳輸:
Send()和recv()這兩個函數用面向連接的socket上進行數據傳輸。
Send()函數原型為:
int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;Len是以字節為單位的數據的長度;flags一般情況下置為0(關該參數的用法可參照man手冊)。
Send()函數返回實際上發送出的字節數,可能會少你希望發送的數據。在程式中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
recv()函數原型為:
int recv(int sockfd,void *buf,int len,unsigned int flags);
Sockfd是接受數據的socket描述符;buf 是存放接收數據的緩沖區;len是緩沖的長度。Flags也被置為0。
Recv()返回實際上接收的字節數,當出現錯誤時,返回-1並置相應的errno值。
Sendto()和recvfrom()用在無連接的數據報socket方式下進行數據傳輸。由本地socket並沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
該函數比send()函數多了兩個參數,to表示目地機的IP位址和埠號資訊,而tolen常常被賦值為
sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
Recvfrom()函數原型為:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一個struct sockaddr類型的變量,該變量保存源機的IP位址及埠號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。
Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。
如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源位址資訊。
如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源位址資訊。
結束傳輸
當所有的數據操作結束以,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
close(sockfd);
你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
int shutdown(int sockfd,int how);
Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
﹒0-------不允許繼續接收數據
﹒1-------不允許繼續發送數據
﹒2-------不允許繼續發送和接收數據,
﹒2-------不允許繼續發送和接收數據,
﹒均為允許則調用close ()
shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。
下面兩隻程式在 linux 利用 socket 來傳送檔案,目前設定為讀取本機端的
TEST.MP3 送到本機端的 GET.MP3
Server.C
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int sockfd, new_fd, numbytes, sin_size;
char buf[1000000];
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
struct stat filestat;
FILE *fp;
//TCP socket
if ( (sockfd = socket(AF_INET,
SOCK_STREAM, 0)) == -1 ){
perror("socket");
exit(1);
}
//Initail, bind to port 2323
my_addr.sin_family
= AF_INET;
my_addr.sin_port = htons(2324);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero( &(my_addr.sin_zero), 8 );
//binding
if ( bind(sockfd, (struct
sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1 ){
perror("bind");
exit(1);
}
//Start listening
if ( listen(sockfd, 10) == -1 ){
perror("listen");
exit(1);
}
//Get file stat
if ( lstat("TEST.MP3",
&filestat) < 0){
exit(1);
} printf("The file size is %lu\n", filestat.st_size);
fp = fopen("TEST.MP3",
"rb");
//Connect
if ( (new_fd = accept(sockfd, (struct
sockaddr*)&their_addr, &sin_size)) == -1 ){
perror("accept"); exit(1);
}
//Sending file
while(!feof(fp)){
numbytes = fread(buf, sizeof(char),
sizeof(buf), fp);
printf("fread %d bytes, ",
numbytes);
numbytes = write(new_fd, buf, numbytes);
printf("Sending %d
bytes\n",numbytes);
}
close(new_fd);
close(sockfd);
return 0;
}
解釋一下 Sending file 的部份:
除非你打算把程式碼寫死成為只能傳送特定大小的檔案,否則,為了讓程式可以傳送任意大小的檔案,我們會在 server 端每次讀取一部分檔案出來傳送,一直讀到檔案結尾為止,所以我們需要一個
while 迴圈,迴圈終止的條件是『fread』讀到檔案結尾。
1.先用 fread 將 fp 開啟的檔案讀到 buf 裡面,讀取 sizeof(char) 大小,sizeof(buf)
次。為避免不同平臺可能會有不同 char 大小問題,一定要用 sizeof(char)來寫比較保險。
2.印出目前讀到多少bytes。
3.用 write 將 buf 裡面的資料寫入上面已經連線好了的 new_fd 這個檔案描述子,第三個參數是用來限制要寫入多少 bytes 過去,因為不見得每次都可以把 buf 填滿,例如每次都讀出1M 來傳送,則最後通常會有不足1M 的剩餘檔案部份需要傳送,所以不能寫 sizeof(buf) ,所以利用 fread 的回傳值來判斷要從 buf 裡面讀出多少來傳送,讀太多的話會送垃圾資訊過去。把傳送過去的大小的值回傳到numbytes裡。
4.印出送了多少 bytes 到遠端。
Client.C
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc, char* argv[])
{
int
sockfd, numbytes;
char
buf[1000000];
struct
sockaddr_in address;
FILE
*fp;
//TCP
socket
if
( ( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 ){
perror("socket");
exit(1);
}
//Initial,
connect to port 2323
address.sin_family
= AF_INET;
address.sin_port
= htons(2324);
address.sin_addr.s_addr
= inet_addr("127.0.0.1");
bzero(
&(address.sin_zero), 8 );
//Connect
to server
if
( connect(sockfd, (struct sockaddr*)&address, sizeof(struct sockaddr)) ==
-1){
perror("connect");
exit(1);
}
//Open
file
if
( (fp = fopen("GET.MP3", "wb")) == NULL){
perror("fopen");
exit(1);
}
//Receive
file from server
while(1){
numbytes
= read(sockfd, buf, sizeof(buf));
printf("read
%d bytes, ", numbytes);
if(numbytes
== 0){
break;
}
numbytes
= fwrite(buf, sizeof(char), numbytes, fp);
printf("fwrite
%d bytes\n", numbytes);
}
fclose(fp);
close(sockfd);
return
0;
}
解釋一下 Receive file from server 的部份:
所以這邊跟 server 都同樣會需要一個 while 迴圈,控制迴圈結束的條件是『接收到大小為零』,這代表檔案已經傳送完了!
所以這邊跟 server 都同樣會需要一個 while 迴圈,控制迴圈結束的條件是『接收到大小為零』,這代表檔案已經傳送完了!
1.用 read 將上面已經連線了的 sockfd 檔案描述子的內容讀取到 buf 裡面,每次最多寫入 sizeof(buf) bytes。將回傳值存在 numbytes 這個變數裡,下兩行的寫檔會需要這個數值。
2.印出本次 read 接收到了多少 bytes 。
3.如果接收到的 bytes 為零的話就結束迴圈,因為我們認為這樣的條件代表檔案傳送完了。
4.用 fwrite 將 buf 的資料寫入 fp ,每次寫入 sizeof(char) bytes,寫入 numbytes 次,寫入太多的話會寫入垃圾資訊,所以要用剛才 read 的回傳值來控制要寫入多少 bytes。
5.印出本次 fwrite 寫入了多少 byets 。
使用socket的Linux上的C語言檔傳輸順序伺服器和客戶
伺服器端程式的編譯
gcc -o file_server file_server.c
用戶端程式的編譯
gcc -o file_client file_client.c
伺服器程式和用戶端程應當分別運行在2台電腦上.
伺服器端程式的運行,在一個電腦的終端執行
./file_server用戶端程式的運行,在另一個電腦的終端中執行
./file_client運行伺服器程式的電腦的IP位址
根據提示輸入要傳輸的伺服器上的檔,該檔在伺服器的運行目錄上。在實際編程和測試中,可以用2個終端代替2個電腦,這樣就可以在一台電腦上測試網路程式,
伺服器端程式的運行,在一個終端執行
./file_server
用戶端程式的運行,在另一個終端中執行
用戶端程式的運行,在另一個終端中執行
./file_client 127.0.0.1
說明: 任何電腦都可以通過127.0.0.1訪問自己. 也可以用電腦的實際IP位址代替127.0.0.1
//////////////////////////////////////////////////////////////////////////////////////
// file_server.c 檔傳輸順序伺服器示例
//////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////
//本檔是伺服器的代碼
#include <netinet/in.h> // for sockaddr_in
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <stdio.h> // for printf
#include <stdlib.h> // for exit
#include <string.h> // for bzero
#define HELLO_WORLD_SERVER_PORT 6666
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char **argv)
{
//設置一個socket位址結構server_addr,代表伺服器internet位址, 埠
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); //把一段記憶體區的內容全部設置為0
server_addr.sin_family = AF_INET; //位址資料族系,同樣設定為AF_INET
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
#include <netinet/in.h> // for sockaddr_in
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <stdio.h> // for printf
#include <stdlib.h> // for exit
#include <string.h> // for bzero
#define HELLO_WORLD_SERVER_PORT 6666
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char **argv)
{
//設置一個socket位址結構server_addr,代表伺服器internet位址, 埠
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); //把一段記憶體區的內容全部設置為0
server_addr.sin_family = AF_INET; //位址資料族系,同樣設定為AF_INET
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
//主機IP位址 in_addr資料格式
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
//主機開啟的通訊埠號 用htons() 寫入
//創建用於internet的流協議(TCP)socket,用server_socket代表伺服器socket
int server_socket = socket(PF_INET,SOCK_STREAM,0);
if( server_socket < 0)
{
printf("Create Socket Failed!");
exit(1);
}
//把socket和socket位址結構聯繫起來
if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
{
printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
exit(1);
}
int server_socket = socket(PF_INET,SOCK_STREAM,0);
if( server_socket < 0)
{
printf("Create Socket Failed!");
exit(1);
}
//把socket和socket位址結構聯繫起來
if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
{
printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
exit(1);
}
//server_socket用於監聽
if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
{
printf("Server Listen Failed!");
exit(1);
}
while (1) //伺服器端要一直運行
{
//定義用戶端的socket位址結構client_addr
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
{
printf("Server Listen Failed!");
exit(1);
}
while (1) //伺服器端要一直運行
{
//定義用戶端的socket位址結構client_addr
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
//接受一個到server_socket代表的socket的一個連接
//如果沒有連接請求,就等待到有連接請求--這是accept函數的特性
//accept函數返回一個新的socket,這個socket(new_server_socket)用於同連接到的客戶的通信
//new_server_socket代表了伺服器和用戶端之間的一個通信通道
//accept函數把連接到的用戶端資訊填寫到用戶端的socket位址結構client_addr中
int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
if ( new_server_socket < 0)
{
printf("Server Accept Failed!\n");
break;
}
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
if (length < 0)
{
printf("Server Recieve Data Failed!\n");
break;
}
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
// int fp = open(file_name, O_RDONLY);
// if( fp < 0 )
FILE * fp = fopen(file_name,"r");
if(NULL == fp )
{
printf("File:\t%s Not Found\n", file_name);
}
else
{
bzero(buffer, BUFFER_SIZE);
int file_block_length = 0;
// while( (file_block_length = read(fp,buffer,BUFFER_SIZE))>0)
while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
{
printf("file_block_length = %d\n",file_block_length);
//發送buffer中的字串到new_server_socket,實際是給用戶端
if(send(new_server_socket,buffer,file_block_length,0)<0)
{
printf("Send File:\t%s Failed\n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
// close(fp);
fclose(fp);
printf("File:\t%s Transfer Finished\n",file_name);
}
//關閉與用戶端的連接
close(new_server_socket);
}
//關閉監聽用的socket
close(server_socket);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// file_client.c 檔傳輸用戶端程式示例
//////////////////////////////////////////////////////////////////////////////////////
//本檔是客戶機的代碼
#include <netinet/in.h> // for sockaddr_in
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <stdio.h> // for printf
#include <stdlib.h> // for exit
#include <string.h> // for bzero
#define HELLO_WORLD_SERVER_PORT 6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
//如果沒有連接請求,就等待到有連接請求--這是accept函數的特性
//accept函數返回一個新的socket,這個socket(new_server_socket)用於同連接到的客戶的通信
//new_server_socket代表了伺服器和用戶端之間的一個通信通道
//accept函數把連接到的用戶端資訊填寫到用戶端的socket位址結構client_addr中
int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
if ( new_server_socket < 0)
{
printf("Server Accept Failed!\n");
break;
}
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
if (length < 0)
{
printf("Server Recieve Data Failed!\n");
break;
}
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
// int fp = open(file_name, O_RDONLY);
// if( fp < 0 )
FILE * fp = fopen(file_name,"r");
if(NULL == fp )
{
printf("File:\t%s Not Found\n", file_name);
}
else
{
bzero(buffer, BUFFER_SIZE);
int file_block_length = 0;
// while( (file_block_length = read(fp,buffer,BUFFER_SIZE))>0)
while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
{
printf("file_block_length = %d\n",file_block_length);
//發送buffer中的字串到new_server_socket,實際是給用戶端
if(send(new_server_socket,buffer,file_block_length,0)<0)
{
printf("Send File:\t%s Failed\n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
// close(fp);
fclose(fp);
printf("File:\t%s Transfer Finished\n",file_name);
}
//關閉與用戶端的連接
close(new_server_socket);
}
//關閉監聽用的socket
close(server_socket);
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// file_client.c 檔傳輸用戶端程式示例
//////////////////////////////////////////////////////////////////////////////////////
//本檔是客戶機的代碼
#include <netinet/in.h> // for sockaddr_in
#include <sys/types.h> // for socket
#include <sys/socket.h> // for socket
#include <stdio.h> // for printf
#include <stdlib.h> // for exit
#include <string.h> // for bzero
#define HELLO_WORLD_SERVER_PORT 6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("Usage: ./%s ServerIPAddress\n",argv[0]);
exit(1);
}
{
if (argc != 2)
{
printf("Usage: ./%s ServerIPAddress\n",argv[0]);
exit(1);
}
//設置一個socket位址結構client_addr,代表客戶機internet位址, 埠
struct sockaddr_in client_addr;
bzero(&client_addr,sizeof(client_addr)); //把一段記憶體區的內容全部設置為0
client_addr.sin_family = AF_INET; //internet協議族
client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自動獲取本機位址
client_addr.sin_port = htons(0); //0表示讓系統自動分配一個空閒埠
//創建用於internet的流協議(TCP)socket,用client_socket代表客戶機socket
int client_socket = socket(AF_INET,SOCK_STREAM,0);
if( client_socket < 0)
{
printf("Create Socket Failed!\n");
exit(1);
}
//把客戶機的socket和客戶機的socket位址結構聯繫起來
if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
{
printf("Client Bind Port Failed!\n");
exit(1);
}
//設置一個socket位址結構server_addr,代表伺服器的internet位址, 埠
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //伺服器的IP位址來自程式的參數
{
printf("Server IP Address Error!\n");
exit(1);
}
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);
//向伺服器發起連接,連接成功後client_socket代表了客戶機和伺服器的一個socket連接
if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
{
printf("Can Not Connect To %s!\n",argv[1]);
exit(1);
}
struct sockaddr_in client_addr;
bzero(&client_addr,sizeof(client_addr)); //把一段記憶體區的內容全部設置為0
client_addr.sin_family = AF_INET; //internet協議族
client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自動獲取本機位址
client_addr.sin_port = htons(0); //0表示讓系統自動分配一個空閒埠
//創建用於internet的流協議(TCP)socket,用client_socket代表客戶機socket
int client_socket = socket(AF_INET,SOCK_STREAM,0);
if( client_socket < 0)
{
printf("Create Socket Failed!\n");
exit(1);
}
//把客戶機的socket和客戶機的socket位址結構聯繫起來
if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
{
printf("Client Bind Port Failed!\n");
exit(1);
}
//設置一個socket位址結構server_addr,代表伺服器的internet位址, 埠
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //伺服器的IP位址來自程式的參數
{
printf("Server IP Address Error!\n");
exit(1);
}
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);
//向伺服器發起連接,連接成功後client_socket代表了客戶機和伺服器的一個socket連接
if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
{
printf("Can Not Connect To %s!\n",argv[1]);
exit(1);
}
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Server:\t");
scanf("%s", file_name);
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
//向伺服器發送buffer中的資料
send(client_socket,buffer,BUFFER_SIZE,0);
// int fp = open(file_name, O_WRONLY|O_CREAT);
// if( fp < 0 )
FILE * fp = fopen(file_name,"w");
if(NULL == fp )
{
printf("File:\t%s Can Not Open To Write\n", file_name);
exit(1);
}
bzero(file_name, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Server:\t");
scanf("%s", file_name);
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
//向伺服器發送buffer中的資料
send(client_socket,buffer,BUFFER_SIZE,0);
// int fp = open(file_name, O_WRONLY|O_CREAT);
// if( fp < 0 )
FILE * fp = fopen(file_name,"w");
if(NULL == fp )
{
printf("File:\t%s Can Not Open To Write\n", file_name);
exit(1);
}
//從伺服器接收資料到buffer中
bzero(buffer,BUFFER_SIZE);
int length = 0;
while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
{
if(length < 0)
{
printf("Recieve Data From Server %s Failed!\n", argv[1]);
break;
}
// int write_length = write(fp, buffer,length);
int write_length = fwrite(buffer,sizeof(char),length,fp);
if (write_length<length)
{
printf("File:\t%s Write Failed\n", file_name);
break;
}
bzero(buffer,BUFFER_SIZE);
}
printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
fclose(fp);
//關閉socket
close(client_socket);
return 0;
}
bzero(buffer,BUFFER_SIZE);
int length = 0;
while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
{
if(length < 0)
{
printf("Recieve Data From Server %s Failed!\n", argv[1]);
break;
}
// int write_length = write(fp, buffer,length);
int write_length = fwrite(buffer,sizeof(char),length,fp);
if (write_length<length)
{
printf("File:\t%s Write Failed\n", file_name);
break;
}
bzero(buffer,BUFFER_SIZE);
}
printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
fclose(fp);
//關閉socket
close(client_socket);
return 0;
}
實作範例: Client端從Server端接收文字檔
t
先ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t
執行./sss (serve端)
等待(client端)
以傳送文字檔t 給client端
(Server端)…..
先ls查詢client端底下有哪些檔名的檔案(沒有要接收的文字檔t)
執行./ccc
127.0.0.1 (client端)
Please Input File Name On Server
請輸入要接收的檔名
等待(Server端)
接受文字檔t 存入client端
ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t
確認與(Server端)的文字檔t 無誤
(Client端)…..