/************************
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不是空指針,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);
}