數據傳輸
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);
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服務。但在發送或接收數據報時,內核會自動為之加上目地和源地址信息。
結束傳輸
當所有的數據操作結束以後,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
close(sockfd);
你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
int shutdown(int sockfd,int how);
Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
·0-------不允許繼續接收數據
·1-------不允許繼續發送數據
·2-------不允許繼續發送和接收數據,
·均為允許則調用close ()
shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。
面向連接的Socket實例
代碼實例中的服務器通過socket連接向客戶端發送字符串"Hello, you are connected!"。只要在服務器上運行該服務器軟件,在客戶端運行客戶軟件,客戶端就會收到該字符串。
該服務器軟件代碼如下:
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 3333 /*服務器監聽端口號 */
#define BACKLOG 10 /* 最大同時連接請求數 */
main()
{
int sockfd,client_fd; /*sock_fd:監聽socket;client_fd:數據傳輸socket */
struct sockaddr_in my_addr; /* 本機地址信息 */
struct sockaddr_in remote_addr; /* 客戶端地址信息 */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket創建出錯!"); exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(SERVPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))
== -1) {
perror("bind出錯!");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen出錯!");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr,
&sin_size)) == -1) {
perror("accept出錯");
continue;
}
printf("received a connection from %sn", inet_ntoa(remote_addr.sin_addr));
if (!fork()) { /* 子進程代碼段 */
if (send(client_fd, "Hello, you are connected!n", 26, 0) == -1)
perror("send出錯!");
close(client_fd);
exit(0);
}
close(client_fd);
}
}
}
服務器的工作流程是這樣的:首先調用socket函數創建一個Socket,然後調用bind函數將其與本機地址以及一個本地端口號綁定,然後調用listen在相應的socket上監聽,當accpet接收到一個連接服務請求時,將生成一個新的socket。服務器顯示該客戶機的IP地址,並通過新的socket向客戶端發送字符串"Hello,you are connected!"。最後關閉該socket。
代碼實例中的fork()函數生成一個子進程來處理數據傳輸部分,fork()語句對於子進程返回的值為0。所以包含fork函數的if語句是子進程代碼部分,它與if語句後面的父進程代碼部分是並發執行的。