歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> linux下簡單socket網絡編程

linux下簡單socket網絡編程

日期:2017/3/3 13:40:35   编辑:Linux技術

在進行socket網絡編程時, 我們需要了解一些必備的知識,例如什麼是socket,ipv4的地址結構,套接字類型等等,不然上來直接看代碼就會暈,當初學習網絡編程時,看書上的例子,總有感覺書上講的都很簡要。再或者講的原理太多把人繞暈。我這裡只想讓大家簡單知道怎麼使用socket進行網絡編程並且給出的例子可以直接使用參考。

1. 什麼是socket

(1) socket 可以看成是用戶進程與網絡協議棧的編程接口。就是說應用層可以看成是用戶進程,傳輸層網絡層數據鏈路層看成網絡協議棧,因為這三個層的傳輸協議TCP,ip等都是已經實現好了的,那麼socket就是連接這兩個進行數據傳遞。

(2) socket不僅可以用於本機的進程間通信,還可以用於網絡上不同主機的進程通信。

2. IPv4套接口地址結構

(1) IPv4套接口地址結構通常也稱為“網際套接字地址結構”,他以socket_in命名,定義在頭文件<netinet/in.h>中

struct sockaddr_in{

uint8_t sin_len;

sa_family_t sin_family;

in_port_t sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

sin_len: 整個sockaddr_in結構體的長度,在4.3BSD-Reno版本之前的第一個成員是sin_family.

sin_family: 指定該地址家族,在這裡必須設為AF_INET(這裡說明使用的協議是IPv4)

sin_port: 端口

sin_addr: IPv4的地址

sin_zero: 一般將其設置為0

3. 通用的地址結構

因為套接字不僅僅用於tcp/ip協議編程,所以必須有一個通用的地址結構

(1) 通用的地址結構用來指定與套接字關聯的地址

struct sockaddr{

uint8_t sin_len;

sa_family_t sin_family;

char sa_data[14];

};

sin_len: 整個sockaddr結構體的長度

sin_family: 指定該地址家族

sa_data: 由sin_family 決定它的形式

4. 網絡字節序

(1) 字節序

1) 大端字節序

最高有效位存儲在最低內存地址處,最低有效位存儲於最高內存地址處。

2)小端字節序

最高有效位存儲在最高內存地址處,最低有效位存儲於最低內存地址處。

(2) 主機字節序

不同的主機有不同的字節序,如x86為小端字節序,motorola 6800為大端字節序,ARM字節序是可配置的。

(3) 網絡字節序

網絡字節序規定為大端字節序

5. 字節序轉換函數

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

說明: 在上述的函數中 ,h代表host,n代表network,s代表short,l代表long。

6. 地址轉換函數

#include<netinet/in.h>

#include<arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);

int_addr_t inet_addr(const char *cp);

char *inet_ntoa(struct in_addr in);

7. 套接字類型

(1) 流式套接字(SOCK_STREAM)

提供面向連接的、可靠的數據傳輸服務,數據無差錯,無重復的發送,且按發送順序接收。

(2) 數據報式套接字(SOCK_DGRAM)

提供無連接服務。不提供無錯保證,數據可能丟失或重復,並且接受順序混亂。

(3)原始套接字(SOCK_RAW)

8. 簡單的點對點回射聊天程序

server端:

#include<sys/socket.h>
 #include<sys/types.h>
 #include<unistd.h>
 #include<stdlib.h>
 #include<netinet/in.h>
 #include<arpa/inet.h>
 #include<stdio.h>
 #include<errno.h>
 #include<string.h>
 
 #define ERR_EXIT(m)\
     do \
     { \
        perror(m);  \
        exit(EXIT_FAILURE); \
     } while(0)
 
 int main()
 {
     int listenfd;
     listenfd = socket(PF_INET, SOCK_STREAM, 0);
     if(listenfd < 0)
         ERR_EXIT("socket");
     struct sockaddr_in seraddr;
     memset(&seraddr, 0, sizeof(seraddr));
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(5788);
     seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
     int bindval = bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr);
     if(bindval < 0)
         ERR_EXIT("bind");
 
     if((listen(listenfd, SOMAXCONN)) < 0)
         ERR_EXIT("listen");
 
     struct sockaddr_in peeraddr;
     socklen_t peerlen = sizeof(peeraddr);
     int conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
     if(conn < 0)
         ERR_EXIT("accept");
 
     char recvbuf[1024];
     while(1)
     {
         memset(recvbuf, 0, sizeof(recvbuf));
         int recvlen = read(conn, recvbuf, sizeof(recvbuf));
         fputs(recvbuf, stdout);
         write(conn, recvbuf, recvlen);
     }
 
     close(listenfd);
     return 0;
 }
client端:

#include<sys/socket.h>
 #include<sys/types.h>
 #include<unistd.h>
 #include<stdlib.h>
 #include<netinet/in.h>
 #include<arpa/inet.h>
 #include<stdio.h>
 #include<errno.h>
 #include<string.h>
 
 #define ERR_EXIT(m)\
     do \
     { \
        perror(m);  \
        exit(EXIT_FAILURE); \
     } while(0)
 
 int main()
 {
     int sockfd;
     sockfd = socket(PF_INET, SOCK_STREAM, 0);
     if(sockfd < 0)
         ERR_EXIT("socket");
     struct sockaddr_in seraddr;
     memset(&seraddr, 0, sizeof(seraddr));
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(5788);
     seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int conn = connect(sockfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
     if(conn < 0)
         ERR_EXIT("connect");
 
     char recvbuf[1024];
     char sendbuf[1024];
     while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
     {
         write(sockfd, sendbuf, sizeof(sendbuf));
         read(sockfd, recvbuf, sizeof(recvbuf));
         fputs(recvbuf, stdout);
         memset(sendbuf, 0, sizeof(sendbuf));
         memset(recvbuf, 0, sizeof(recvbuf));
     }
 
     close(sockfd);
     return 0;
 }
這裡有個問題,是當server關閉之後,在登錄時,登不上,會提示bind: Address already in use,地址綁定不上。

因為這時候端口被占用沒有被釋放,我們可以用命令:netstat -an | grep TIME_WAIT查看

解決辦法:

在bind之前盡可能調用setsockopt來設置REUSEADDR套接字選項。

使用REUSEADDR選項可以使得不必等待TIME_WAIT狀態消失就可以重啟服務器。

int on = 1;  //等於1代表開啟
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    ERR_EXIT("setsockopt");
9. 多個客戶端(多進程)

客戶端程序不變

服務器端:

#include<sys/socket.h>
 #include<sys/types.h>
 #include<unistd.h>
 #include<stdlib.h>
 #include<netinet/in.h>
 #include<arpa/inet.h>
 #include<stdio.h>
 #include<errno.h>
 #include<string.h>
 
 #define ERR_EXIT(m)\
     do \
     { \
        perror(m);  \
        exit(EXIT_FAILURE); \
     } while(0)
 
void do_service(int conn)
 {
  char recvbuf[1024];
  while(1)
  {
       memset(recvbuf, 0, sizeof(recvbuf));
       int recvlen = read(conn, recvbuf, sizeof(recvbuf));
       if(recvlen == 0)
       {
           printf("clent close\n");
           break;
       }
       if(recvlen == -1)
       {
           printf("read data error\n");
           break;
       }
       fputs(recvbuf, stdout);
       write(conn, recvbuf, recvlen);
   }
}
 int main()
 {
     int listenfd;
     listenfd = socket(PF_INET, SOCK_STREAM, 0);
     if(listenfd < 0)
         ERR_EXIT("socket");
     struct sockaddr_in seraddr;
     memset(&seraddr, 0, sizeof(seraddr));
     seraddr.sin_family = AF_INET;
     seraddr.sin_port = htons(5788);
     seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
  
     int on = 1;
     if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
         ERR_EXIT("setsockopt");
  
     int bindval = bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr)
     if(bindval < 0)
         ERR_EXIT("bind");
   if((listen(listenfd, SOMAXCONN)) < 0)
		ERR_EXIT("listen");
 struct sockaddr_in peeraddr;
 socklen_t peerlen = sizeof(peeraddr);
 pid_t pid;
 while(1)
 {
    int conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
    if(conn < 0)
       ERR_EXIT("accept");
    printf("address:%s, port:%d\n",inet_ntoa(peeraddr.sin_addr), ntohs(pe
	pid = fork();
	if (pid == -1)
		ERR_EXIT("fork");
	if (pid == 0)
		{
			close (listenfd);
			do_service (conn);
			exit (EXIT_SUCCESS);   //client close,so close subprocess
											//if not closed, it is will continue
											//recv connect.
        }
    else
       {
            close(conn);
        }
   }
 
     close(listenfd);
     return 0;
 }
在這裡使用fork來開啟一個進程, 當有一個連接過來,fork一個進程去處理客戶端的連接。

Copyright © Linux教程網 All Rights Reserved