您的位置:

Linux网络编程详解

一、套接字(Socket)

套接字是进行网络编程时非常重要的概念,它是一种通信的机制,可以在不同主机之间进行通信。套接字在网络编程中扮演着重要的角色,下面我们来一步步地了解它。

1.创建套接字


#include <sys/types.h>
#include <sys/socket.h>

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

socket()函数可以创建一个新的套接字。其中,domain参数指定了通信协议的领域,如AF_INET表示IPv4协议族,AF_INET6表示IPv6协议族。type参数指定了套接字的类型,如SOCK_STREAM表示TCP类型。protocol参数指定使用的协议,一般为0。

2.绑定套接字


#include <sys/types.h>
#include <sys/socket.h>

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

bind()函数将本地的Socket地址和创建的Socket进行绑定。其中,sockfd参数即为socket()函数返回的文件描述符。addr参数是一个指向本地Socket地址结构体的指针。addrlen参数是该结构体的长度。

3.监听套接字


#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);

listen()函数将sockfd指定的Socket转换成被动式的Socket,backlog指定了连接请求队列的最大长度。

4.接收连接请求


#include <sys/types.h>
#include <sys/socket.h>

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

accept()函数从连接请求队列中取得一个连接请求,生成一个全新的Socket进行通信。其中,sockfd为listen()函数创建的Socket。addr和addrlen参数表示请求连接的客户端的地址信息。

二、进程和线程

进程和线程是操作系统中非常基础的概念,本篇文章着重介绍在网络编程中如何使用它们。

1.进程

进程是操作系统中进行资源分配和调度的基本单位。在网络编程中,我们可以使用fork()函数创建一个新进程,让进程分别运行不同的代码。


#include <unistd.h>

pid_t fork(void);

fork()函数会创建一个新的进程,并将父进程的数据复制到子进程中。父子进程是并发的,但是它们运行的是同一个代码。我们可以通过进程间的通信(IPC),使它们在运行过程中进行信息的交互。

2.线程

线程是进程中的更小单位,它可以轻松的与其他线程共享进程数据,便于实现并发。在Linux系统中,线程的实现依赖于POSIX的线程库。


#include <pthread.h>

int pthread_create(pthread_t \*thread, const pthread_attr_t \*attr, void *(*start_routine) (void *), void \*arg);

pthread_create()函数可以创建一个新的线程,其中参数thread是线程的标识符,attr指向线程属性的结构,start_routine是线程的入口地址,arg参数将传递给入口函数。

三、并发编程

在网络编程中,进程和线程是实现并发编程的基本手段。当多个客户端同时连接服务器时,我们需要一种方法来同时处理这些连接请求,这时候并发编程便派上了用场。

1.多进程实现并发


#include <sys/wait.h>

pid_t pid;

switch (pid = fork()) {
    case -1:
        perror("fork");
        break;
    case 0:
        /* child */
        exit(0);
        break;
    default:
        /* parent */
        waitpid(pid, NULL, 0);
        break;

上述代码中,我们可以在主进程中调用fork()函数创建一个子进程来处理一个请求。这样,就可以同时处理多个请求。通过使用进程的基于文件描述符的机制可以实现进程之间的通信。

2.多线程实现并发


#include <pthread.h>

int threads_num;
pthread_t threads[THREAD_NUM];

/* create threads */
for (int i = 0; i < threads_num; i++) {
    if (pthread_create(&threads[i], NULL, worker, NULL)) {
        perror("pthread_create failed");
        exit(1);
    }
}

/* join threads */
for (int i = 0; i < threads_num; i++) {
    if (pthread_join(threads[i], NULL)) {
        perror("pthread_join failed");
        exit(1);
    }
}

上述代码中,我们使用pthread_create()函数创建若干个线程,并通过pthread_join()函数等待线程结束。线程可以共享数据,且线程和线程之间的切换速度比进程之间的切换速度要快得多。

四、网络编程实践

下面我们通过一个简单的例子来演示Linux网络编程的过程。

1.创建Socket


#include <sys/types.h>
#include <sys/socket.h>

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket error");
    exit(1);
}

上述代码中,我们使用socket()函数创建一个新的Socket。其中,参数AF_INET表示使用IPv4协议族,SOCK_STREAM表示使用TCP协议。

2.绑定Socket


#include <sys/types.h>
#include <sys/socket.h>

struct sockaddr_in server_addr;
bzero(\&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(port);

int ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == -1) {
    perror("bind error");
    exit(1);
}

上述代码中,我们使用bind()函数将Socket与本地的IP地址和端口进行绑定。

3.监听Socket


#include <sys/types.h>
#include <sys/socket.h>

int ret = listen(sockfd, backlog);
if (ret == -1) {
    perror("listen error");
    exit(1);
}

上述代码中,我们使用listen()函数将Socket转化为可接受的请求,backlog参数指定了连接请求队列的长度。

4.接受连接请求


#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
if (connfd == -1) {
    perror("accept error");
    exit(1);
}
printf("accept from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

上述代码中,我们使用accept()函数从连接请求队列中取出一个连接请求生成一个全新的Socket,客户端的地址信息保存在client_addr结构体中,我们可以通过inet_ntoa()和ntohs()函数将网络字节顺序的IP地址和端口转换为可读形式。

5.接收和发送消息


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

上述代码中,我们使用recv()和send()函数进行接收和发送消息,分别对应于TCP中的read()和write()函数。

结论

以上便是Linux网络编程的详解,我们介绍了套接字、进程和线程、并发编程、以及通过一个简单例子演示了网络编程的基本流程。在进一步的使用和探索中,相信本篇文章为读者提供了很好的指引。