一、简介

    在Linux系统中,网络编程是通过socket接口来实现的。socket是一种特殊的I/O接口,也是文件描述符。socket是进程间通信的常用机制,特别的,可以通过网络实现不同主机之间的通信。

    每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。 socket 也有一个类似于打开文件的函数调用,该函数返回一个整型的 socket 描述符,随后的连接建立、数据传输等操作都是通过 socket 来实现的。

常用的socket类型:

/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,              /* Sequenced, reliable, connection-based
                                   byte streams.  */
#define SOCK_STREAM SOCK_STREAM
  SOCK_DGRAM = 2,               /* Connectionless, unreliable datagrams
                                   of fixed maximum length.  */
#define SOCK_DGRAM SOCK_DGRAM
  SOCK_RAW = 3,                 /* Raw protocol interface.  */
#define SOCK_RAW SOCK_RAW
  SOCK_RDM = 4,                 /* Reliably-delivered messages.  */
#define SOCK_RDM SOCK_RDM
  SOCK_SEQPACKET = 5,           /* Sequenced, reliable, connection-based,
                                   datagrams of fixed maximum length.  */
#define SOCK_SEQPACKET SOCK_SEQPACKET
  SOCK_DCCP = 6,                /* Datagram Congestion Control Protocol.  */
#define SOCK_DCCP SOCK_DCCP
  SOCK_PACKET = 10,             /* Linux specific way of getting packets
                                   at the dev level.  For writing rarp and
                                   other similar things on the user level. */
#define SOCK_PACKET SOCK_PACKET

  /* Flags to be ORed into the type parameter of socket and socketpair and
     used for the flags parameter of paccept.  */

  SOCK_CLOEXEC = 02000000,      /* Atomically set close-on-exec flag for the
                                   new descriptor(s).  */
#define SOCK_CLOEXEC SOCK_CLOEXEC
  SOCK_NONBLOCK = 00004000      /* Atomically mark descriptor(s) as
                                   non-blocking.  */
#define SOCK_NONBLOCK SOCK_NONBLOCK
};

①SOCK_STREAM

流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺序性。

②SOCK_DGRAM

数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议 UDP。

③SOCK_RAW

原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

二、重要接口及数据结构介绍

A、数据结构

sockaddr 和 sockaddr_in,这两个结构类型都是用来保存 socket 信息的,这两个数据类型是等效的,可以相互转化,通常 sockaddr_in 数据类型使用更为方便。在建立 socketadd 或 sockaddr_in 后,就可以对该 socket 进行适当的操作了。如下所示:

 

 sa_family 字段可选的常见值:

 

 B、主要API

int socket(int domain, int type, int protocol);

该函数用于建立一个 socket 连接,可指定 socket 类型等信息。在建立了 socket 连接之后,可对 sockaddr 或 sockaddr_in 结构进行初始化,以保存所建立的 socket 地址信息。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

该函数是用于将本地 IP 地址绑定到端口号,若绑定其他 IP 地址则不能成功。另外,它主要用于 TCP 的连接,而在 UDP 的连接中则无必要。

int listen(int sockfd, int backlog);

在服务端程序成功建立套接字和与地址进行绑定之后,还需要准备在该套接字上接收新的连接请求。此时调用 listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

服务端程序调用 listen()函数创建等待队列之后,调用 accept()函数等待并接收客户端的连接请求。它通常从由 bind()所创建的等待队列中取出第一个未处理的连接请求。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

该函数在 TCP 中是用于 bind()的之后的 client 端,用于与服务器端建立连接,而在 UDP 中由于没有了 bind()函数,因此用 connect()有点类似 bind()函数的作用。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

这两个函数分别用于发送和接收数据,可以用在 TCP 中,也可以用在 UDP 中。当用在 UDP 时,可以在 connect()函数建立连接之后再用。

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

这两个函数的作用与 send()和 recv()函数类似,也可以用在 TCP 和 UDP 中。当用在 TCP 时,后面的几个与地址有关参数不起作用,函数作用等同于 send()和 recv();当用在 UDP 时,可以用在之前没有使用connect()的情况下,这两个函数可以自动寻找指定地址并进行连接。

下面来看下socket接口调用流程,下图为使用TCP协议时:

 

 下图为使用UDP协议通信时:

 

 三、实例操作

通过编写下面实例来学习巩固,本实例通过SOCK_STREAM类型通信。代码分为客户端和服务器端两部分,其中服务器端首先建立起 socket,然后与本地端口进行绑定,接着就开始接收从客户端的连接请求并建立与它的连接,接下来,接收客户端发送的消息。客户端则在建立 socket 之后调用 connect()函数来建立连接,之后发送消息到服务端。

/*
* FILE: server.c
* NOTE: socket网络编程学习,server端程序
*
* TIME: 2021年11月12日23:32:53
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define   PORT  1234
#define   BUFFER_SIZE   1024
#define   MAX_QUE_CONN_NM   5

int main(int argc, char *argv[])
{
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int sin_size, recvBytes;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];

    /* 创建socket */
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        return -1;
    }

    printf("socket id = %d\n", sockfd);

    /* 设置sockaddr_in 结构体参数 */
    server_sockaddr.sin_family = AF_INET; //IPv4
    server_sockaddr.sin_port = htons(PORT); //端口号
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);

    /* 允许重复使用本地地址与套接字进行绑定 */
    int i = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    /* 绑定到端口号(TCP使用) */
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind success!\n");

    /*调用 listen()函数,创建未处理请求的队列*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        return -1;
    }
    printf("Listening...\n");

    /*调用accept(), 阻塞等待客户端的连接*/
    client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size);
    if (client_fd == -1)
    {
        perror("accept");
        return -1;
    }

    /*调用recv(), 阻塞接收客户端的请求*/
    memset(buf, 0, sizeof(buf));
    recvBytes = recv(client_fd, buf, BUFFER_SIZE, 0);
    if (recvBytes == -1)
    {
        perror("recv");
        return -1;
    }

    printf("Recv a message:%s \n", buf);

    /* 关闭socket */
    close(sockfd);
    
    return 0;
}
/*
* FILE: client.c
* NOTE: socket网络编程学习,Client端程序
*
* TIME: 2021年11月12日23:32:53
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define   PORT  1234
#define   BUFFER_SIZE   1024
#define   MAX_QUE_CONN_NM   5

int main(int argc, char *argv[])
{
    struct sockaddr_in serv_addr;
    int sendBytes;
    int sockfd, client_fd;
    char buf[BUFFER_SIZE];
    struct hostent *host;

    /* 地址解析函数 */
    host = gethostbyname(argv[1]);
    if (host == NULL)
    {
        perror("gethostbyname");
        return -1;
    }

    memset(buf, 0, sizeof(buf));
    sprintf(buf, "%s", argv[2]);

    /* 创建socket */
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        return -1;
    }

    printf("socket id = %d\n", sockfd);

    /* 设置sockaddr_in 结构体参数 */
    serv_addr.sin_family = AF_INET; //IPv4
    serv_addr.sin_port = htons(PORT); //端口号
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero), 8);

    /* 调用 connect 函数主动发起对服务器端的连接 */
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1)
    {
        perror("connect");
        return -1;
    }

    /* 发送消息给服务器端 */
    sendBytes = send(sockfd, buf, strlen(buf), 0);
    if (sendBytes == -1)
    {
        perror("send");
        return -1;
    }
    
    printf("Send a message:%s \n", buf);

    /* 关闭socket */
    close(sockfd);
    
    return 0;
}

编译:gcc client.c -o client;

           gcc server.c -o server

运行测试,先运行./server,然后再运行./client localhost 'hello world!',server成功收到client发送的字符串。

 

 

 

posted on 2021-11-12 23:57  沉默的思想  阅读(188)  评论(0)    收藏  举报