Skip to main content

Linux Socket Programming How To

·1671 words·8 mins
Linux Socket Programming
Table of Contents

Overview
#

  1. First, the network. When we see this word, we immediately think of IP addresses and port numbers. So, what are the respective roles of IP addresses and ports?

    • An IP address is like an ID card, uniquely identifying a computer. Each computer has only one IP address.
    • Ports provide an access channel. Servers generally identify specific services through well-known port numbers. For example, for every TCP/IP implementation, the TCP port number for an FTP server is 21, the TCP port number for each Telnet server is 23, and the UDP port number for each TFTP (Trivial File Transfer Protocol) server is 69.
  2. Network socket transmission protocols are TCP and UDP. This learning session will utilize the TCP protocol for data transmission.

    Differences between TCP and UDP:

    1. TCP is connection-oriented (like making a phone call where you need to dial and establish a connection first); UDP is connectionless, meaning there is no need to establish a connection before sending data.
    2. TCP provides reliable service. That is, data transmitted through a TCP connection is error-free, not lost, not duplicated, and arrives in order; UDP makes a best effort to deliver, meaning it does not guarantee reliable delivery.
    3. TCP is byte-stream oriented; in essence, TCP treats data as a continuous, unstructured stream of bytes; UDP is message-oriented. UDP does not have congestion control, so network congestion will not reduce the sending rate of the source host (very useful for real-time applications such as IP telephony and real-time video conferencing).
    4. Each TCP connection can only be point-to-point; UDP supports one-to-one, one-to-many, many-to-one, and many-to-many interactive communication.
    5. The TCP header has an overhead of 20 bytes; the UDP header has a smaller overhead of only 8 bytes.
    6. The logical communication channel of TCP is a full-duplex reliable channel, while UDP is an unreliable channel.

Byte Order
#

Byte order refers to the order in which multi-byte data is stored in computer memory or transmitted over a network.

Common Orders:

  1. Little endian: Stores the least significant byte at the starting address.

  2. Big endian: Stores the most significant byte at the starting address.

    Example: If we write 0x1234abcd to memory starting at address 0x0000, the result will be:

    Address Little endian Big endian
    0x0000 0xcd 0x12
    0x0001 0xab 0x34
    0x0002 0x34 0xab
    0x0003 0x12 0xcd

    Network byte order = Big endian, X86 series CPUs use little endian.

Socket Development
#

Socket development is similar to file operations. First, create a socket to get a file descriptor, then bind, listen, read/write, and close.

Development Steps:

Linux Socket TCP

  1. Socket Creation

    Function Prototype:

    #include <sys/types.h>      /* See NOTES */
    #include <sys/socket.h>
    
    // Returns a file descriptor on success, -1 on error
    int socket(int domain, int type, int protocol);
    

    Parameter Description:

    • domain: Specifies the protocol family to be used, usually AF_INET, which represents the Internet Protocol family (TCP/IP protocol family);

      • AF_INET IPv4 Internet domain
      • AF_INET6 IPv6 Internet domain
      • AF_UNIX Unix domain
      • AF_ROUTE Routing socket
      • AF_KEY Key socket
      • AF_UNSPEC Unspecified
    • type: Specifies the type of socket:

      • SOCK_STREAM: Provides reliable, connection-oriented communication streams; it uses the TCP protocol, thus ensuring the correctness and order of data transmission.
      • SOCK_DGRAM: Defines a connectionless service where data is transmitted in independent packets, which are unordered and do not guarantee reliability or error-free delivery. It uses the UDP protocol.
      • SOCK_RAW: Allows programs to use lower-level protocols. Raw sockets allow direct access to lower layers such as IP or ICMP, which is powerful but less convenient to use and mainly used for the development of some protocols.
    • protocol: Usually set to 0.

      • 0 Selects the default protocol corresponding to the type.
      • IPPROTO_TCP TCP transmission protocol
      • IPPROTO_UDP UDP transmission protocol
      • IPPROTO_SCTP SCTP transmission protocol
      • IPPROTO_TIPC TIPC transmission protocol
  2. Bind Function: bind()

    Functionality: Used to bind an IP address and port number to a socket file descriptor (sockfd).

    Function Prototype:

    #include <sys/types.h>      /* See NOTES */
    #include <sys/socket.h>
    
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    Parameter Description:

    • sockfd: A socket file descriptor.
    • addr: A pointer to a struct sockaddr type, containing IP address and port number information, pointing to the protocol address structure to be bound to sockfd. This address structure varies depending on the address protocol family used when creating the socket.
    struct sockaddr {
                    sa_family_t sa_family;  // Protocol family
                    char sa_data[14];       // IP address + port
               } ;
    

    Equivalent Replacement:

    #include<linux/in.h>
    struct sockaddr_in {
      __kernel_sa_family_t  sin_family;     // Protocol family
      __be16                sin_port;       // Port number
      struct in_addr        sin_addr;       // IP address structure
      unsigned char         sin_zero[8];    // Padding, no actual meaning, just for byte alignment
                                            // with the sockaddr structure in memory, so that
                                            // the two can be mutually converted.
    };
    
  3. Listen Function: listen()

    Function Prototype:

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    
    // Returns 0 on successful execution, -1 otherwise
    int listen(int sockfd, int backlog);
    

    Parameter Description:

    • sockfd: The server-side socket file descriptor returned by the socket system call.
    • backlog: Specifies the maximum number of pending connection requests allowed in the queue, i.e., the maximum number of connections. Functionality: Sets the maximum number of connections that can be handled. Initially, only the listen mode of the socket is set. The listen function is only used on the server side. The server process does not know which client to connect to, so it does not actively initiate connections but continuously listens for connection requests from other client processes and then responds to these connection requests. A server process can handle connections from multiple client processes simultaneously.
  4. Accept Function: accept()

    Function Prototype:

    #include <sys/types.h>      /* See NOTES */
    #include <sys/socket.h>
    
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    Parameter Description:

    • sockfd: The server-side socket file descriptor returned by the socket system call.
    • addr: Used to return the protocol address of the connected peer (client).
    • addrlen: The length of the client address.

    Return Value:

    This function returns a new socket file descriptor for the connected peer (client). The kernel creates a connected socket (representing the completion of the TCP three-way handshake) for each client connection accepted by the server process. When the server finishes serving a given client, the corresponding connected socket is closed.

  5. Address Conversion API

    Function Prototype:

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    // Functionality: Converts a string in the form "192.168.1.123" to a network-recognizable format and stores it in inp.
    int inet_aton(const char *cp, struct in_addr *inp);
    
    // Functionality: Converts a network-formatted IP address to a string.
    char *inet_ntoa(struct in_addr in);
    
  6. Byte Order Conversion API

    Function Prototype:

    #include <arpa/inet.h>
    
    // Returns the value in network byte order.
    uint32_t htonl(uint32_t hostlong);
    
    // Returns the value in network byte order.
    uint16_t htons(uint16_t hostshort);
    
    // Returns the value in host byte order.
    uint32_t ntohl(uint32_t netlong);
    
    // Returns the value in host byte order.
    uint16_t ntohs(uint16_t netshort);
    

    Function Description:

    • h represents host
    • n represents net
    • s represents short (two bytes)
    • l represents long (four bytes)

    The above four functions can be used to convert between host byte order and network byte order. Sometimes INADDR_ANY can be used, which tells the operating system to obtain the address itself.

  7. Common Data Sending and Receiving Functions

    The read() and write() functions are used for reading and writing bytes during socket communication. They differ slightly from the read functions in file I/O because the number of bytes they input or output may be less than requested. You can test this; the number of bytes input or output is the number of valid bytes.

    #include <unistd.h>
    
    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
    

    The second set of read and write functions, send() and recv(), can also be used.

    • send(): Sends data

      Function Prototype:

      #include <sys/types.h>
      #include <sys/socket.h>
      
      ssize_t send(int sockfd, const void *buf, size_t len, int flags);
      

      Parameter Description:

      • sockfd: The socket file descriptor of the newly connected peer (client), which is the return value of the accept function.
      • buf: Stores the data to be sent.
      • len: The length of the data to be sent.
      • flags: Control options, usually set to 0.

      Return Value: Returns the length of the data sent on successful execution, -1 otherwise.

    • recv() function: Receives data

      Function Prototype:

      #include <sys/types.h>
      #include <sys/socket.h>
      
      ssize_t recv(int sockfd, void *buf, size_t len, int flags);
      

      Parameter Description:

      • sockfd: The socket file descriptor of the newly connected peer (client), which is the return value of the accept function.
      • buf: Stores the received data.
      • len: The length of the data to be received.
      • flags: Control options, usually set to 0.
  8. Client Connection Function: connect()

    Functionality: This function is used by the client to establish a connection with the server.

    Function Prototype:

    #include <sys/types.h>      /* See NOTES */
    #include <sys/socket.h>
    
    // Returns 0 on successful execution, -1 otherwise
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    Parameter Description:

    • sockfd: The socket file descriptor of the target server.
    • addr: A pointer to the address structure containing the server’s IP address and port number.
    • addrlen: The length of the addr address, usually set to sizeof(struct sockaddr).

Programming Implementation of Server-Client Connection Establishment
#

server.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
//#include <linux/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main()
{

  int s_fd;

  struct sockaddr_in s_addr;
  struct sockaddr_in c_addr;

  memset((void *)&s_addr,0,sizeof(struct sockaddr_in));
  memset((void *)&c_addr,0,sizeof(struct sockaddr_in));

  //1.socket
  s_fd=socket(AF_INET,SOCK_STREAM,0);
  printf("s_fd=%d\n",s_fd);

  if(s_fd==-1)
  {
     perror("socket");
     exit(0);
  }
  //2.bind
  s_addr.sin_family=AF_INET;
  s_addr.sin_port  =htons(8888);
  inet_aton("your_local_ip_address",&s_addr.sin_addr);
  bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
  //3.listen
  listen(s_fd,10);
  //4.accept

  int cnt =sizeof(struct sockaddr_in);
  int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&cnt);

  if(c_fd==-1)
  {
    perror("accept");
    exit(0);
    }else{
    printf("get success:%s\n",inet_ntoa(c_addr.sin_addr));
  }
  //5.read
  char readbuff[128]={0};
  int nread=read(c_fd,readbuff,128);

  if(nread==-1)
  {
   perror("read");
  }
  else{
  printf("msg data from clinet:%d,%s\n",nread,readbuff);
   memset(readbuff,0,128);
  }
  //6.write
  write(c_fd,"I get your connect",strlen("I get your connect"));
  //用read()检测客户端是否退出
  nread=read(c_fd,readbuff,128);
  if(nread==0)
  printf("connect end\n");
  else
  printf("nread:%d\n",nread);

  //关闭
  close(s_fd);
  close(c_fd);
  return 0;

}

client.c


#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>


int main()
{

  int c_fd;

  struct sockaddr_in c_addr;

  memset((void *)&c_addr,0,sizeof(struct sockaddr_in));

  //1.socket
  c_fd=socket(AF_INET,SOCK_STREAM,0);

  if(c_fd==-1)
  {
     perror("socket");
     exit(0);
  }

  c_addr.sin_family=AF_INET;
  c_addr.sin_port  =htons(8888);//端口号一般设置为5000~9000
  inet_aton("your_local_ip_address",&c_addr.sin_addr);
  //connect
 int ct=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in));

 if(ct==-1)
 {
    perror("connect");
    exit(0);
 }

  write(c_fd,"I get connect",strlen("I get connect"));

  char readbuff[128];
  int nread=read(c_fd,readbuff,128);
  if(nread==-1)
  {
   perror("read");
  }
  else{
  printf("read data:%s\n",readbuff);
  }
  close(c_fd);
  return 0;
}

Related

Linux如何实时监控网卡流量
·168 words·1 min
Linux Network
Linux nc 命令详解
·173 words·1 min
Linux NC
Linux的NAT与端口转发
·364 words·2 mins
Linux NAT