文章目录

套接字编程的基本模式

客户端/服务器

  客户端/服务器模式就是基本的网络编程模式,简称C/S(即Client/Server)模式。需要注意的是这里的客户端、服务器指的是软件层面的意思而不是硬件,即客户端、服务器是分别运行在两台电脑上的两个软件。

  一般而言,服务器的IP地址和端口号是要众所周知的,并且服务器要24小时不间断运行;相反,客户端的IP地址和端口号就没有众所周知的要求,也没有不间断运行的要求。这里的原因很简单,就是因为服务器是被访问方,大家都要知道它的地址才能访问它,并且它不知道什么时候有人会访问,因此要一直运行。

一些与客户端有关的基本函数

  与客户端有关的基本函数如下:

#include <sys/socket.h>

int socket(int family, int type, int protocol);
/* 返回值:若调用成功,则返回对应的套接字描述符;若出错则返回-1 */

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
 /*返回值:若成功返回0,若出错则返回-1*/

  其中socket在笔记一中已经说明了。

  connect函数用于客户端尝试对服务器进行连接。其中sockfd是客户端的套接字描述符;servaddr是指向服务器套接字地址结构的指针;addrlen是服务器套接字地址结构的指针。

一些与服务器有关的基本函数

  与服务器有关的基本函数如下:

#include <sys/socket.h>

int socket(int family, int type, int protocol);
/* 返回值:若调用成功,则返回对应的套接字描述符;若出错则返回-1 */

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
 /*返回值:若成功返回0,若出错则返回-1*/

int listen(int sockfd, int backlog);
/*返回值:若成功返回0,若出错则返回-1*/

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
/*返回值:若成功则为非负描述符,若出错则为-1*/

  同样地,socket省略不讲了。

  bind函数用于将一个套接字绑定上一个地址结构。之前说过服务器的地址是众所周知的,所以它的套接字地址必须明确。其中sockfd是用于绑定的套接字;myaddr就是指向服务器地址结构的指针(该地址结构将被用于绑定);addrlen就是该地址结构的大小。

  listen函数用于监听连接,在TCP协议中通信前需要先建立一个逻辑连接,listen函数也仅由TCP服务器调用。其中sockfd是服务器的监听套接字,backlog是服务器在同一时刻最多可以监听多少个连接。

  accept函数用于创建一个已连接套接字。其中第一个参数sockfd是监听套接字;第二个参数和第三个参数被称为 “值-结果” 参数,虽然accept的返回值只有一个(返回一个新的套接字描述符,即已连接套接字描述符),但是accept还会将客户端套接字的地址结构和长度返回并存放在第二个和第三个参数中。

TCP型客户端-服务器的通信示意图

  用一张图来描述调用函数的过程应该会好理解一些:
套接字编程的基本模式(网络编程Linux_C -&gt; 笔记二)-LMLPHP

TCP通信示例代码

// 服务器的代码
// FILENAME: server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int lis_sockfd, conn_sockfd; /*声明:监听套接字和连接套接字的描述符*/
    struct sockaddr_in server_address, client_address; /*声明:服务器和客户端的套接字地址结构*/
    int server_len, client_len; /*声明:套接字地址结构的长度*/

    lis_sockfd = socket(AF_INET, SOCK_STREAM, 0); /*创建监听套接字*/

	/*为服务器套接字地址结构的各个成员进行赋值*/
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(4000);

    server_len = sizeof(server_address);/*获取服务器地址结构的长度*/

    bind(listen_sockfd, (struct sockaddr *)&server_address, server_len); /*为监听套接字绑定众所周知的地址*/

    listen(lis_sockfd, 5); /*将lis_sockfd正式声明为监听套接字*/

    while(1){
        char ch;
        printf("server waiting\n");
        client_len = sizeof(client_address);
        conn_sockfd = accept(lis_sockfd, (struct sockaddr *)&client_address, (socklen_t *)&client_len); /*建立连接*/
        read(conn_sockfd, &ch, 1);
        ch++;
        write(conn_sockfd, &ch, 1);
        close(conn_sockfd);
    }
    exit(0);
}
//客户端代码
//FILENAME: client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int client_sockfd;
    int server_len;
    struct sockaddr_in server_address;

    int result;
    char ch;

    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = htons(4000);
    server_len = sizeof(server_address);

    result = connect(client_sockfd, (struct sockaddr *)&server_address, server_len);

    if(result == -1){
        perror("oops: client");
        exit(1);
    }

    while(1)
    {
        ch = getchar();
        write(client_sockfd, &ch, 1);
        read(client_sockfd, &ch, 1);
        printf("char from server = %c\n", ch);
        break;
    }
    close(client_sockfd);
    exit(0);
}
10-05 15:42