/************************
c語言編寫的tcp socket通訊的server端。
可以持續監聽myprot指定的連接埠
列印連接埠接收到的字元流
標頭檔因為角括弧被轉義,所以用了引號
************************/
#include "stdio.h"
#include "stdlib.h"
#include "errno.h"
#include "string.h"
#include "sys/types.h"
#include "netinet/in.h"
#include "sys/socket.h"
#include "sys/wait.h"
int main(int argc,char **argv)
{
int sockfd, new_fd;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
unsigned int sin_size,myport,listnum;
myport = 9785; //綁定的連接埠號碼
listnum = 10;
/*************************************************
Socket介面:是TCP/IP網路的API,Socket介面定義了許多的函數,可以
在此基礎上開發Internet上的TCP/IP網路編程
Create Socket: int socket(int domain, int type, int protoco);
Argument Description:domain 指明所有協議族,通常是PF_INET(TCP/IPV4)
當然他也可以支援IPV6,和更多的網路通訊協定,根據
具體的應用來選擇
type 分SOCK_STREAM(TCP),SOCK_DGRAM(UDP),SOCK_RAW
(允許程式使用底層協議)
protolol 通常賦值“0”
Return Value: Socket描述符是一個指向內部資料結構的指標,它指向描述符表
入口。調用Socket函數時,socket執行體將建立一個Socket,
實際上"建立一個Socket"意味著為一個Socket資料結構分配存
儲空間。Socket執行體為你管理描述符表。
**************************************************/
if((sockfd = socket(PF_INET,SOCK_STREAM,0)) == -1 )
{
perror("socket is error/n;");
exit(1);
}
my_addr.sin_family = PF_INET; //指定協議族
/***************************************************
電腦資料存放區有兩種位元組優先順序:高位位元組優先和低
位位元組優先。Internet上資料以高位位元組優先順序在網路
上傳輸,所以對於在內部是以低位位元組優先方式儲存資料
的機器,在Internet上傳輸資料時就需要進行轉換,否則
就會出現資料不一致。
htonl():把32位值從主機位元組序轉換成網路位元組序
htons():把16位值從主機位元組序轉換成網路位元組序
ntohl():把32位值從網路位元組序轉換成主機位元組序
ntohs():把16位值從網路位元組序轉換成主機位元組序
****************************************************/
my_addr.sin_port = htons(myport); //如果填入0,系統將隨機播放一個連接埠
my_addr.sin_addr.s_addr = INADDR_ANY; //填入本機IP地址
bzero(&(my_addr.sin_zero),0);
/**************************************************
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
Description:建立socket,並綁定該連接埠,申明已經使用,別人不會再佔用該連接埠
Argument Description: sockfd:是Socket系統調用返回的socket 描述符
my_addr:需要綁定在通訊端上的地址,
是類似於以下結構體的變數
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
Return Value: 成功執行時,返回0。失敗返回-1,errno被設定出錯資訊
***************************************************/
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr)) == -1)
{
perror("bind is error/n");
exit(1);
}
/**************************************************
int listen(int sockfd, int ListenSum)
Description: Listen()函數使socket處於被動的監聽模式,
並為該socket建立一個輸入資料隊列,將
到達的服務要求儲存在此隊列中,直到程
序處理它們。
Argument Description:sockfd: 是Socket系統調用返回的socket 描述符
ListenSum: 指定在請求隊列中允許的最大請求數
Return Value: 成功執行時,返回0。失敗返回-1,errno被設定出錯資訊
***************************************************/
if(listen(sockfd,listnum) == -1)
{
perror("listen is error/n");
exit(1);
}
printf("start to listen/n");
while(1)
{
sin_size = sizeof(struct sockaddr_in);
/***********************************************************
int accept(int sockfd, void *addr, int *addrlen)
Description:accept()函數讓伺服器接收客戶的串連請求。
在建立好輸入隊列後,伺服器就調用accept
函數,然後睡眠並等待客戶的串連請求。
Argument Description:sockfd: 是Socket系統調用返回的socket 描述符
addr : 通常是一個指向sockaddr_in變數的指標,
該變數用來存放提出串連請求服務的主
機的資訊(某台主機從某個連接埠發出該請求)
addrten: 通常為一個指向值為
sizeof(struct sockaddr_in)的整型指標變數
Return Value: 失敗返回-1,errno被設定出錯資訊
成功返回 當accept函數監視的socket收到串連請求時,
socket執行體將建立一個新的 socket,執
行體將這個新socket和請求串連進程的地址
聯絡起來,收到服務要求的初始socket仍可
以繼續在以前的 socket上監聽,同時可以在
新的socket描述符上進行資料轉送操作
***************************************************************/
if((new_fd = accept(sockfd,(struct sockaddr *)&their_addr,&sin_size)) == -1)
{
perror("accept is error/n");
continue;
}
printf("server:got connection from %s/n",inet_ntoa(their_addr.sin_addr));
char *p;
char sock_buf[1024];
bzero(sock_buf, 1024);
p = sock_buf;
int rval=0;
/**************************************************************************
int recv(SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是伺服器應用程式都用recv函數從TCP串連的另一端接收資料。
該函數的第一個參數指定接收端通訊端描述符;
第二個參數指明一個緩衝區,該緩衝區用來存放recv函數接收到的資料;
第三個參數指明buf的長度;
第四個參數一般置0。
這裡只描述同步Socket的recv函數的執行流程。當應用程式調用recv函數時,recv先等待s的發送緩衝中的資料被協議傳送完畢,如果協
議在傳送s的發送緩衝中的資料時出現網路錯誤,那麼recv函數返回SOCKET_ERROR,如果s的發送緩衝中沒有資料或者資料被協議成功發送完畢
後,recv先檢查通訊端s的接收緩衝區,如果s接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一直等待,只到協議把資料接收完畢。當協議把
資料接收完畢,recv函數就把s的接收緩衝中的資料copy到buf中(注意協議接收到的資料可能大於buf的長度,所以在這種情況下要調用幾次
recv函數才能把s的接收緩衝中的資料copy完。recv函數僅僅是copy資料,真正的接收資料是協議來完成的),recv函數返回其實際copy
的位元組數。如果recv在copy時出錯,那麼它返回SOCKET_ERROR;如果recv函數在等待協議接收資料時網路中斷了,那麼它返回0。
***************************************************************************/
if ((rval = recv(new_fd, p, 1024, 0)) < 0)
{
printf("recv errror/n");
}
else
{
printf("recv %s/n",p);
}
/**************************************************************************
fork一個子進程對此次串連同父進程並行運行 ***************************************************************************/
if(!fork())
{
/***********************************************************************
int send(int sockfd, const void *msg, int len, int flags)
Description:通過子進程發送資訊給用戶端
Argument Description: sockfd: 是你想用來傳輸資料的socket描述符
msg : 是一個指向要發送資料的指標
Len : 是以位元組為單位的資料的長度
flags : 一般情況下置為0(這個參數涉及到阻塞和非阻塞問題)
************************************************************************/
if(send(new_fd,"hello,HuHan/n",14,0) == -1)
{
perror("send is error/n");
close(new_fd);
exit(0);
}
}
/******************************************************************************
當所有的資料操作結束以後,你可以調用close()函數來釋放該socket,從而停止在
該socket上的任何資料操作
如你可以關閉某socket的寫操作而允許繼續在該socket上接受資料,直至讀入所有資料。
int shutdown(int sockfd,int how);
Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
·0-------不允許繼續接收資料
·1-------不允許繼續發送資料
·2-------不允許繼續發送和接收資料
shutdown在操作成功時返回0,在出現錯誤時返回-1共置相應errno。
*******************************************************************************/
close(new_fd);
/****************************************************************************
pid_t waitpid(pid_t pid, int *stat_loc, int option)
pid參數指定需要等待的子進程的PID,如果是-1,waitpid將返回任一子進程的資訊,與
wati()一樣,如果stat_loc不是null 指標,waitpid將把狀態資訊寫到所指向的位置。option
參數允許我們改變waitpid的行為,其中最有用的一個選項是WNOHANG,他的作用是防止
waitpid()調用將調用者執行掛起,可以用這個選項來尋找是否有子進程已經結束,如果沒有
將繼續執行。
*****************************************************************************/
waitpid(-1,NULL,WNOHANG);
}
}
用戶端程式:
#i nclude <stdlib.h>
#i nclude <stdio.h>
#i nclude <errno.h>
#i nclude <string.h>
#i nclude <netdb.h>
#i nclude <sys/types.h>
#i nclude <netinet/in.h>
#i nclude <sys/socket.h>
int main(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{
printf("Usage:%s hostname portnumber/a/n",argv[0]);
exit(1);
}
/*gethostbyname
可以通過主機名稱得到主機的
IP
地址
*/
if((host=gethostbyname(argv[1]))==NULL)
{
printf("Gethostname error/n");
exit(1);
}
/*portnumber
為連接埠號碼
*/
if((portnumber=atoi(argv[2]))<0)
{
printf("Usage:%s hostname portnumber/a/n",argv[0]);
exit(1);
}
/*
客戶程式開始建立
sockfd
描述符
*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
printf("Socket Error:%s/a/n",strerror(errno));
exit(1);
}
/*
客戶程式填充服務端的資料
*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
/*
主機位元組序轉換為網路位元組序
*/
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr=*((struct in_addr *)host->h_addr);
/*
客戶程式發起串連請求
*/
if(connect(sockfd,(struct sockaddr *)(&server_addr),
sizeof(struct sockaddr))==-1)
{
printf("Connect Error:%s/a/n",strerror(errno));
exit(1);
}
/*
串連成功了
*/
if((nbytes=read(sockfd,buffer,1024))==-1)
{
printf("Read Error:%s/n",strerror(errno));
exit(1);
}
buffer[nbytes]='/0';
printf("I have received:%s/n",buffer);
/*
結束通訊
*/
close(sockfd);
exit(0);
}