歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux管理 >> Linux網絡

Linux網絡編程--服務器客戶端(TCP實現)

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和端口都是內核隨機分配的。

Copyright © Linux教程網 All Rights Reserved