Linux下的一個服務器客戶端的小程序,基於TCP的實現;服務器可以同時接受多個客戶的接入,通過子進程處理客戶請求,下面的例子中,服務器只將客戶的IP和端口以及發送的信息顯示,然後原樣的將客戶發送的信息發送給客戶。客戶端僅僅是輸入信息以及顯示收到的信息。
TCP通信的模式如下圖,比較固定,對著圖編代碼就可以了:
服務器的main函數:
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr; //IPv4 address
/*socket*/
listenfd = socket(AF_INET, SOCK_STREAM, 0);//創建一個TCP的socket
if (-1 == listenfd) {
perror("socket erro.");
return -1;
}
/*bind*/
//首先初始化server的IP地址和端口,然後再與剛剛創建的socket綁定
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;//設置協議簇
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//綁定本機的網卡
servaddr.sin_port = htons(1234);//綁定端口號,端口號可以隨便取,大於1024就可以了
if (-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("bind error.");
return -1;
}
/*listen*/
//到這裡已經有了一個綁定了IP地址和端口號的socket了,但是這個socket是個主動的socket,
//而作為server需要的是一個等待別的接入的被動的socket,所以得調用listen將這個socket設置為監聽狀態
//第二個參數表示服務器正在處理客戶接入時的等待隊列長度。
if (-1 == listen(listenfd, 10)) {
perror("listen error.");
return -1;
}
while (1) {
clilen = sizeof(cliaddr);
//調用accept等待客戶的接入,同時accept會用第二個參數返回客戶的IP地址,
//通過第三個參數返回IP地址的實際大小,同時這個參數也是個值-結構參數,也就是
//在傳遞這個參數的時候,先給這個參數一個初始的值,然後函數中會根據具體的情況修改這個值,
//所以這裡傳遞的是指針。
//當客戶接入後,將返回一個成功和客服連接的socket描述符,通過讀寫這個socket即可實現和客戶的通信了
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
if (-1 == connfd) {
if (EINTR == errno)
continue;
perror("accept error.");
return -1;
}
//通過fock創建子進程來處理客戶請求,這裡只是顯示客戶的IP地址、端口號和發送的文字,
//並將客戶發送的文字回傳給客戶。
if (0 == (childpid=fork())) {//fock返回0 說明是子進程
//這裡並沒有關閉服務器的監聽socket,只是將其引用計數減一,因為fork出來的子進程對父進程做了拷貝,
//所以這個監聽socket的引用計數將由1變成2,而內核只有在一個socket的引用計數變為0才回去關閉它
close(listenfd);
//通過和客戶連接的socket和客戶通信
str_echo(connfd, cliaddr);
return 0;
}
//父進程將和客戶連接的socket的引用計數減一,同樣並沒有關閉這個socket
close(connfd);
}
return 0;
}
服務器處理客戶請求:
#define BSIZE 100
void str_echo(int sockfd, struct sockaddr_in cliaddr)
{
ssize_t n;
char buf[BSIZE];
while ((n=read(sockfd, buf, BSIZE))) {//讀取客戶發送的信息
buf[n] = '\0';
printf("IP: %s, PORT: %d: %s\n", \
inet_ntoa(cliaddr), ntohs(cliaddr.sin_port), buf); //輸出信息
n_write(sockfd, buf, n);//將受到的信息發送回客戶,n_write的實現下面給出
}
}
客戶端程序相對簡單:
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (2 != argc) {
printf("usage: ./client 127.0.0.1");//
return -1;
}
/*socket*/
sockfd = socket(AF_INET, SOCK_STREAM, 0); //創建一個socket
if (-1 == sockfd) {
perror("socket error.");
return -1;
}
/*connect*/
//首先初始化要連接的服務器的IP地址和端口號,然後調用connect去連接這個服務器
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(1234);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
if (-1 == connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("connect error.");
return -1;
}
//連接成功後就可以通過這個socket來和服務器通信了
str_cli(stdin, sockfd);
return 0;
}
n_write 和 readline
/*write n bytes to fd*/
ssize_t n_write (int fd, void *buf, size_t n)
{
size_t nleft = n;
ssize_t nwriten;
char *bufp = buf;
while (nleft > 0) {
if ((nwriten = write (fd, bufp, nleft)) <= 0) {
if (EINTR == errno)
nwriten = 0;
else
return -1;
}
nleft -= nwriten;
bufp += nwriten;
}
return n;
}
/*read line from fd*/
ssize_t readline (int fd, void *buf, size_t maxlen)
{
ssize_t n, rc;
char c, *bufp;
bufp = buf;
for (n = 1; n < maxlen; n ++) {
again:
if (1 == (rc = read (fd, &c, 1))) {
*bufp ++ = c;
if ('\n' == c)
break; /*newline is stored*/
} else if (rc == 0) {
*bufp = 0;
return (n - 1); /*EOF, n-1 bytes were read*/
} else {
if (EINTR == errno) /*interrupt*/
goto again;
return -1; /*Erro, set the errno by read ()*/
}
}
*bufp = 0;
return n;
}
運行結果:
因為客戶端沒有指定IP地址和端口,所以其IP和端口都是內核隨機分配的。