Linux 进程间套接字通信(Socket)编程

Linux 进程间套接字通信(Socket)编程


转载链接:https://blog.csdn.net/violet_echo_0908/article/details/49670901


姓名:罗学元        学号:21181214375     学院:广州研究院


【嵌牛导读】Linux进程间套接字通信编程


【嵌牛鼻子】Linux 进程间套接字及通信编程


【嵌牛提问】Linux进程间套接字编程如何进行,包含哪些部分


一、流式socket的接口及作用

socket的接口函数声明在头文件

 

1.创建套接字——socket系统调用

该函数来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字,其原型如下:

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

函数中的三个参数分别对应前面所说的三个套接字属性。protocol参数设置为0表示使用默认协议。

 

2.命名(绑定)套接字——bind系统调用

该函数把通过socket调用创建的套接字命名,从而让它可以被其他进程使用。对于AF_UNIX,调用该函数后套接字就会关联到一个文件系统路径名,对于AF_INF,则会关联到一个IP端口号。函数原型如下:

int bind(int socket, const struct sockaddr *address, size_t a ddress_len)

成功时返回0,失败时返回-1;

 

3.创建套接字队列(监听)——listen系统调用

该函数用来创建一个队列来保存未处理的请求。成功时返回0,失败时返回-1。其原型如下:

int listen(int socket, int backlog)

backlog用于指定队列的长度,等待处理的进入连续的个数最多不能超过这个数字,否则往后的连接将被拒绝,导致客户的连接请求失败。调用后,程序一直会监听这个IP端口,如果有连接请求,就把它加入到这个队列中。

4.接受连接——accept系统调用

该系统调用用来等待客户建立对该套接字的连接。accept系统调用只有当客户程序试图连接到由socket参数指定的套接字上时才返回,也就是说,如果套接字队列中没有未处理的连接,accept将阻塞直到有客户建立连接为止。accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字的描述符,新套接字的类型和服务器监听套接字类型是一样的。它的原型如下:


int accept(int socket, struct sockaddr *address, size_t *address_len)

1

address为连接客户端的地址,参数address_len指定客户结构的长度,如果客户地址的长度超过这个值,它将会截断。

 

5.请求连接——connect系统调用

该系统调用用来让客户程序通过在一个未命名套接字和服务器监听

套接字之间建立连接的方法来连接到服务器。它的原型如下:

int connect(int socket, const struct sockaddr *address, size_t

address_len)

参数socket指定的套接字连接到参数address指定的服务器套接字。

成功时返回0,失败时返回-1。

 

6.关闭socket——close系统调用

该系统调用用来终止服务器和客户上的套接字连接,我们应该总是在连接的两段(服务器和客户)关闭套接字。

 

二、进程使用流式socket进行通信

1.服务器程序:

#include

#include

#include

#include

#include

#include

#include


int main()

{

    int server_sockfd = -1;

    int client_sockfd = -1;

    int client_len = 0;

    struct sockaddr_in server_addr;

    struct sockaddr_in client_addr;


//创建流套接字

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);    

//设置服务器接收的连接地址和监听的端口

server_addr.sin_family = AF_INET;     //指定网络套接字

server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   //接受所有IP地址的连接

server_addr.sin_port = htons(9736);     //绑定到9736端口


//绑定(命名)套接字

    bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

listen(server_sockfd, 5);      //创建套接字队列,监听套接字

signal(SIGCHLD, SIG_IGN);      //忽略子进程停止或退出信号


    while(1){

        char ch = '\0';

        client_len = sizeof(client_addr);

        printf("Server waiting\n");


//接受连接,创建新的套接字

        client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);


        if(fork()==0){

//子进程中,读取客户端发过来的信息,处理信息,再发送给客户端

            read(client_sockfd, &ch, 1);

            sleep(5);

            ch++;

            write(client_sockfd, &ch, 1);

            close(client_sockfd);

        }

        else{

//父进程中,关闭套接字

            close(client_sockfd);

        }

    }

}

这是一个服务器程序,它首先创建套接字,然后绑定一个端口再监听套接字,忽略子进程的停止消息等,然后它进入循环,一直循环检查是否有客户连接到服务器,如果有,则调用fork创建一个子进程来处理请求。利用read系统调用来读取客户端发来的信息,利用write系统调用来向客户端发送信息。这个服务器的工作非常简单,就是把客户发过来的字符+1,再发送回给客户。

 

2.客户端程序:

#include

#include

#include

#include

#include

#include

#include

#include


int main()

{

    int sockfd = -1;

    int len = 0;

    struct sockaddr_in address;

    int result;

    char ch = 'A';


//创建流套接字

sockfd = socket(AF_INET, SOCK_STREAM, 0);     //设置要连接的服务器的信息

address.sin_family = AF_INET;     //使用网络套接字

address.sin_addr.s_addr = inet_addr("127.0.0.1");    //服务器地址

address.sin_port = htons(9736);      //服务器所监听的端口

    len = sizeof(address);

result = connect(sockfd, (struct sockaddr*)&address, len);    //连接到服务器


    if(result == -1)

    {

        perror("ops:client\n");

        exit(1);

    }


write(sockfd, &ch, 1);      //发送请求给服务器


read(sockfd, &ch, 1);       //从服务器获取数据

    printf("char from server = %c\n", ch);

    close(sockfd);

    exit(0);

}

这是一个客户程序,它同样要先创建套接,然后连接到指定IP端口服务器,如果连接成功,就用write来发送信息给服务器,再用read获取服务器处理后的信息,再输出。

 

 

 

三.函数解释

1.socket

功能:创建一个套接口()。

原型:

#include

SOCKET PASCAL FAR socket( int af, int type, int protocol);

af:一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。


type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。


protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

SOCK_STREAM提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。

SOCK_DGRAM支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。

SOCK_STREAM类型的套接口为全双向的字节流。对于流类套接口,在接收或发送数据前必需处于已连接状态。用connect()调用建立与另一套接口的连接,连接成功后,即可用send()和recv()传送数据。当会话结束后,调用closesocket()。带外数据根据规定用send()和recv()来接收。


实现SOCK_STREAM类型套接口的通讯协议保证数据不会丢失也不会重复。如果终端协议有缓冲区空间,且数据不能在一定时间成功发送,则认为连接中断,其后续的调用也将以WSAETIMEOUT错误返回。


SOCK_DGRAM类型套接口允许使用sendto()和recvfrom()从任意端口发送或接收数据报。如果这样一个套接口用connect()与一个指定端口连接,则可用send()和recv()与该端口进行数据报的发送与接收。

 

2.htonl(), ntohl(), htons(), ntohs() 函数

在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons(), ntohl(), ntohs(),htons()这4个函数。


网络字节顺序与本地字节顺序之间的转换函数:


htonl()–“Host to Network Long”

ntohl()–“Network to Host Long”

ntohs()–“Network to Host Short”


之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO


网络字节顺序NBO(Network Byte Order): 按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。主机字节顺序(HBO,Host Byte Order): 不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。


如Intel x86结构下, short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12


如IBM power PC结构下, short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78


由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序. 在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换.

 

 

 

3.int socket(int domain, int type,int protocol)

domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信

type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.


socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况

 

 

 

4.int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

sockfd:是由socket调用返回的文件描述符.

addrlen:是sockaddr结构的长度.

my_addr:是一个指向sockaddr的指针.

有sockaddr的定义

struct sockaddr

{

    unisgned short as_family;

    char sa_data[14];

};


不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替.在其中有sockaddr_in的定义


struct sockaddr_in{

    unsigned short sin_family;

    unsigned short int sin_port;

    struct in_addr sin_addr;

    unsigned char sin_zero[8];

}

我们主要使用Internet所以sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号.sin_zero[8]是用来填充的. bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样

 

 

 

5.int listen(int sockfd,int backlog)

sockfd:是bind后的文件描述符.

backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度.

listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.

 

 

 

6.int accept(int sockfd, struct sockaddr *addr,int *addrlen)

sockfd:是listen后的文件描述符.

addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.

bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1

 

 

 

7.int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)

sockfd:socket返回的文件描述符.

serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址

addrlen:serv_addr的长度


connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1


总的来说网络程序是由两个部分组成的–客户端和服务器端.它们的建立步骤一般是:


服务器端

socket–>bind–>listen–>accept


客户端

socket–>connect

你可能感兴趣的:(Linux 进程间套接字通信(Socket)编程)