client可以連續傳訊息給server,
但是,server不能連續傳訊息給client,
像是server要傳檔案給client,分割好幾份,
則在server傳給client每一份之間,client必需傳一個訊息給server,
如此一來,server才可以傳下一份分割給client
下面我用gcc寫了一個非常簡單的傳輸檔案的程式:
server.c
//==========================include file==================================
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//========================================================================
//==========================define variable===============================
#define PORT 9734
#define FILEBUFFERSIZE 1024
//========================================================================
//==========================global variable===============================
//========================================================================
//==========================function phototypes===========================
//========================================================================
int main(int argc,char *argv[])
{
int listenfd,connfd;
// 在c語言中,要用到struct時,前面必需要加上struct,在c++中則不需要
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int server_len,client_len;
char temp[FILEBUFFERSIZE];
int ReadByte,WriteByte;
char end[]="#end#"; // 用來判斷是否傳送檔案結束
FILE *fp;
if (argc!=2) {
printf("Insrtuction error!!\n");
printf("./server 'file'\n");
exit(1);
}
if ((fp=fopen(argv[1],"rb"))==NULL) {
fputs("open file error\n",stderr);
exit(1);
}
// divide - test
while((ReadByte=fread(temp,sizeof(char),FILEBUFFERSIZE,fp))>0) {
printf("%d\n",ReadByte);
memset(temp,0,FILEBUFFERSIZE);
}
// divide - test
// 1.建立一個新的通訊端:socket(int domain,int type,int protocol),並回傳參數值
listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 2.1.設定為Unix network socket,表示可以在兩個不同的電腦之間傳資料
server_address.sin_family = AF_INET;
// 2.2.server的ip位址,這裡必需是server的ip位址
// inet_addr(), this function translate IP address into specified format for socket
server_address.sin_addr.s_addr=INADDR_ANY;
// server_address.sin_port = 9734;
// 2.3.轉換成little-end,因為intel是little-end
server_address.sin_port = htons(PORT);
// 記錄server端的設定(port,ip,sin_family...)的資料長度
server_len = sizeof(server_address);
// 3.Socket 出入口需Binding到TCP address
if(bind(listenfd,(struct sockaddr*) &server_address, server_len)==-1){
printf("bind error");
exit(1);
}
// 4.建立一個queue以接收其他程式所送達的連線要求,queue的大小為5
if (listen(listenfd,5)==-1){
printf("listening error");
exit(1);
}
// 當產生了一個佇列(queue)後,再來就是要把queue中的連結請求(Connect Request)讀出
while(1)
{
// temp存放要傳送的檔案
char temp[FILEBUFFERSIZE];
printf("server waiting \n");
client_len = sizeof(client_address); // 了解client的設定資料結構長度
// 5.以accept指令來等待client端送去連線要求(connect request)
// 由client_address獲得client端的網路位置
// connfd即由新的connected socket 所存放參數的地方。用來和client 進行資料輸出入
connfd = accept(listenfd,(struct sockaddr *)&client_address, &client_len);
if (connfd==-1){
printf("Error:accept()\n");
exit(1);
}
// 由connfd讀取資料存到temp的位址中,buffer大小為1024 bytes
read(connfd, &temp, FILEBUFFERSIZE);
if (printf("ch : %s \n",temp)==-1){
printf("Read error!\n");
exit(1);
}
else
{
if (strncmp(temp,"get",strlen("get"))==0)
{
printf("File transmite...\n");
fp=fopen(argv[1],"rb");
if (fp==NULL) {
printf("open file error!!\n");
exit(1);
}
memset(temp,0,sizeof(temp));
ReadByte=fread(temp,sizeof(char),FILEBUFFERSIZE,fp);
while(ReadByte>0){
printf("read %d Byte\n",ReadByte);
WriteByte=write(connfd,temp,ReadByte);
printf("transmite %d bytes\n",WriteByte);
memset(temp,0,sizeof(temp));
ReadByte=read(connfd,temp,FILEBUFFERSIZE);
printf("%s\n",temp);
memset(temp,0,FILEBUFFERSIZE);
ReadByte=fread(temp,sizeof(char),FILEBUFFERSIZE,fp);
}
}
WriteByte=write(connfd,end,sizeof(end));
printf("Transmite %d bytes\n",WriteByte);
}
// 關掉connected socket
close(connfd);
}
// 關掉listen的socket
close(listenfd);
fclose(fp);
return 0;
}
//==========================function implementation=======================
//========================================================================
client.c
//============================include file===================================
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//===========================================================================
//=============================define variable===============================
#define PORT 9734
#define FILEBUFFERSIZE 1024
//===========================================================================
int main(int argc,char *argv[])
{
int sockfd;
int ReadByte;
struct sockaddr_in address;
int client_len,result;
FILE *fp;
char server_addr[15];
if (argc!=2) {
fputs("./client get",stderr);
exit(1);
}
if((fp=fopen("copy","wb"))==NULL) {
printf("create error\n");
exit(1);
}
fclose(fp);
printf("Please input server's ip:");
scanf("%s",server_addr);
// 建立一個socket
sockfd = socket(AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET;
// 以下存放的必需是server的ip位址
address.sin_addr.s_addr=inet_addr(server_addr);
address.sin_port = htons(PORT);
client_len = sizeof(address);
// Client端則呼叫connect()功能,要求與Server主機建立連接通道
result = connect(sockfd, (struct sockaddr *)&address,client_len);
if(result == -1) // 若傳回-1則表示連線失敗
{
perror("oops: client1");
fclose(fp);
exit(1) ;
}
// 傳送資料到server端
if (write( sockfd, &(*argv[1]) , FILEBUFFERSIZE)==-1){
printf("write error\n");
fclose(fp);
exit(1);
}
else {
// 接收檔案
char temp[FILEBUFFERSIZE];
memset(temp,0,FILEBUFFERSIZE);
ReadByte=read(sockfd,temp,FILEBUFFERSIZE);
printf("get the %d bytes\n",ReadByte);
fp = fopen("copy","wb");
if (fp==NULL) {
printf("open file error!!\n");
exit(1);
}
else {
while(ReadByte>0) {
if (strncmp(temp,"#end#",strlen("#end#"))==0) {
printf("The connect is over\n");
break;
}
fwrite(temp,sizeof(char),ReadByte,fp);
write(sockfd,"I got\n",sizeof("I got\n"));
memset(temp,0,FILEBUFFERSIZE);
ReadByte=read(sockfd,temp,FILEBUFFERSIZE);
printf("I got the %d bytes\n",ReadByte);
}
fclose(fp);
}
// 當client接收到資料後,必需要write資料到server才能再read資料!,這樣才叫被動啊!!
}
close( sockfd); // 關掉socket介面
return 0;
}
執行:
[user@host ~]$ ./server source
[user@host ~]$ ./client get
就會在目錄下出現一個copy的檔案
這裡用到的新函數
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
由FILE指標指到的檔案讀取單位為size byte,且個數上限為count的資料,並存到ptr所指到的指標位址空間,回傳值為實際讀取到的byte數
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
由FILE指標指到的檔案寫入單位為size byte,且個數上限為count的資料,並存到ptr所指到的指標位址空間,回傳值為實際寫入到的byte數
int socket(int domain, int type, int protocol)
– Doman : AF_UNIX UNIX files system sockets
AF_INET → UNIX network sockets
AF_ISO → ISO standard protocols
AF_NS → Xerox Network Systems protocols
AF_IPX → Novell IPX protocol
– Type : SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
– Protocol : default 0
int bind(int socket, const struct socket * address, size_t address_len);
socket : return value from socket function
– 利用socket函數產生socket後所回傳的參數值
• *address : struct sockaddr_un, struct sockaddr_in
– 對映於通訊端(socket)所宣告的資料結構
• address_len : address length
– 參數為資料結構的大小
當成功後,函式會傳回0以代表成功,若失敗即傳回-1。
建立一個queue以接收其他程式所送達的連線要求(request)
– int listen(int socket, int backlog)
• socket : return value from socket function
– 為所新建立的socket descriptor
• Backlog : Queue length
– 代表所欲建立的queue大小為多少
當成功後,函式會傳回0以代表成功,若失敗即傳回-1。
當產生了一個佇列(queue)後,再來就是要把queue中的連結請求(Connect Request)讀出
• 其函數的原型如下:
– int connect(int socket, const struct sockaddr
*address, size_t address_len)
• socket : return value from socket function
– 為新建立的socket descriptor
• *address : struct sockaddr_un, struct sockaddr_in
– 對映於通訊端(socket)所宣告的資料結構
• address_len : address length
– 為address資料結構的大小
SOCKET accept(SOCKET s,struct sockaddr* addr,int* addrlen);
s:一個設定為listen狀態的socket
sddr:Client端位址資訊,由函式自動產生填入
addrlen:sddr長度,由函式自動產生
回傳值:-1表示錯誤,否則傳回另一個包含Client端資訊的新socket
descriptor,作為傳送資料用
傳進accept()的listen socket本身並沒有辦法作資料的傳輸,所以必須透過accept()產生一個包含通訊協定、Server、Client資訊的新socket,利用他就可以進行資料的傳輸了
int count=send( int sockfd, char *buffer, int buflen, int flags )
–count:傳送資料的大小
–buffer:資料傳送的指標
–buflen:資料長度
–flag:通常設為0
–return為實際傳送的byte數
–Or write
• int write(int sockfd, char * buffer, int buflen);
Int count=recv( int sockfd, char *buffer, int buflen, int flags )
–Count:接收到的資料大小
–buffer:資料接收後儲存位址的指標
–buflen:資料長度
–flag:通常設為0
–return為實際傳送的byte數
–Or read
• int read(int sockfd, char * buffer, int buflen);
int close (int sockfd) ;
用來關閉socket,並且終止TCP連線
參考資料:
Linux socket 編程入門
網路程式規畫
TCP通訊協定的server程式及client程式
socket編程原理
使用Socket完成FTP檔案存取
程式設計範例及習題 Chapter 5 網路程式範例
Socket Programming HOWTO
以後可能會用到:
[C/C++] 解決Socket連續Bind同一個Port的問題
UNIX Network Programming Source Code
沒有留言:
張貼留言