浅谈Linux C Socket编程

Socket

    在 UNIX/Linux 系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。

    UNIX/Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。注意:网络连接也是一个文件,它也有文件描述符。

    我们可以通过socket()函数来创建一个网络连接,socket()函数的返回值就是文件描述符。有了文件描述符,我们就可以用read()读取从远程计算机传来的数据,用write()向远程计算机写入数据。

    socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,在设计模式中就是一个门面模式。1.所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的语抦。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。2.Socket是连接运行在网络上的两个程序间的双向通信的端点。3.网络通讯其实指的就是Socket间的通讯。通讯的两端都有Socket,数据在两个Socket之间通过IO来进行传输。
image.png
image.png

socket类型

流格式套接字(SOCK_STREAM)

    流格式套接字(SOCK_STREAM)也叫“面向连接的套接字”,它使用TCP协议,从而保证了数据传输的正确性和顺序性。它有以下几个特征:1、数据在传输过程中不会丢失。2、数据是按照顺序传输的。3、数据的发送和接受不是同步的。

数据报格式套接字(SOCK_DGRAM)

    数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,它使用UDP协议,一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它有以下几个特征:1、强调快速传输而非传输顺序。2、传输的数据可能丢失也可能损毁。3、限制每次传输数据的大小。4、数据的发送和接受是同步的。

Linux下socket程序 这里只写了基于TCP的socket代码

服务端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <errno.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    int server_sockfd; //服务端套接字
    int client_sockfd; //客户端套接字
    int len;
    struct sockaddr_in my_add; //服务器网络地址结构体
    struct sockaddr_in remote_addr; //客户端网络地址结构体
    socklen_t sin_size;
    char buf[BUFSIZ]; //数据传送的缓冲区
    memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
    my_add.sin_family = AF_INET; //设置为IP通信
    my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
    my_add.sin_port = htons(8000); //服务器端口号
    
    //创建服务器端套接字--IPV4协议 ,面向连接通信,TCP协议
    if((server_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
        perror("socket");
        return 1;
    }
    
    //将套接字绑定到服务器的网络地址上
    if (bind(server_sockfd, (struct sockaddr *)&my_add, sizeof(struct sockaddr))<0) {
        perror("bind");
        return 1;
    }
    
    //监听连接请求--监听队列长度为5
    listen(server_sockfd, 5);
    sin_size = sizeof(struct sockaddr_in);
    
    //等待客户端连接请求到达
    if ((client_sockfd=accept(server_sockfd, (struct sockaddr *)&remote_addr, &sin_size))<0) {
        perror("accept");
        return 1;
    }
    
    printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
    len = send(client_sockfd, "welcome to my server\n", 21, 0); //发送欢迎信息
    
    //接受客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数
    while ((len==recv(client_sockfd, buf, BUFSIZ, 0))>0) {
        buf[len]='\0';
        printf("receive:%s\n",buf);
        if (send(client_sockfd, buf, len, 0)<0) {
            perror("write");
            return 1;
        }
    }
    
    close(client_sockfd);
    close(server_sockfd);
    
    return 0;
}

客户端代码:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <netdb.h>


int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    int client_sockfd;
    int len;
    struct sockaddr_in remota_addr; //服务器端网络地址结构体
    char buf[BUFSIZ];  //数据传送的缓冲区
    memset(&remota_addr,0,sizeof(remota_addr)); //数据初始化--清零
    remota_addr.sin_family = AF_INET;  //设置为IPV4通信
    remota_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    remota_addr.sin_port = htons(8000); //服务器端口号
    
    //创建客户端套接字--Ipv4协议,面向连接通信,TCP协议
    //成功,返回0 ,失败返回-1
    if ((client_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
        perror("socket");
        return 1;
    }
    
    //将套接字绑定到服务器的网络地址上
    if (connect(client_sockfd, (struct sockaddr *)&remota_addr, sizeof(struct sockaddr))<0) {
        perror("connect");
        return 1;
    }
    
    printf("connect to server\n");
    
    len = recv(client_sockfd, buf, BUFSIZ, 0); //接受服务器端消息
    buf[len]='/0';
    printf("%s",buf);  //打印服务器端消息
    
    //循环的发送信息并打印接受消息--recv返回接收到的字节数,send返回发送的字节数
    while (1) {
        printf("Enter string to send");
        scanf("%s",buf);
        if (!strcmp(buf,"quit")) {
            break;
        }
        
        len=send(client_sockfd,buf,strlen(buf),0);
        len=recv(client_sockfd,buf,BUFSIZ,0);
        buf[len]='/0';
        printf("received:%s\n",buf);
        
    }
    
    close(client_sockfd);
    
    return 0;
}

在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字。


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

(1)、af为地址族(Address Family),也就是IP地址类型,常用的有AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,AF_INET6 表示 IPv6 地址。也可以使用 PF 前缀(PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6)。

(2)、type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字)和 SOCK_DGRAM(数据报套接字/无连接的套接字)。

(3)、protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

这种套接字称为 TCP 套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议
这种套接字称为 UDP 套接字:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议

也可以写成:
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字

bind()函数


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

sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。


    int server_sockfd; //服务端套接字
    struct sockaddr_in my_add; //服务器网络地址结构体
    char buf[BUFSIZ]; //数据传送的缓冲区
    memset(&my_add,0,sizeof(my_add)); //数据初始化--清零
    my_add.sin_family = AF_INET; //设置为IP通信
    my_add.sin_addr.s_addr = INADDR_ANY; //服务器IP地址--允许连接到所有本地地址上
    my_add.sin_port = htons(8000); //服务器端口号
    
    //创建服务器端套接字--IPV4协议 ,面向连接通信,TCP协议
    if((server_sockfd=socket(PF_INET, SOCK_STREAM, 0))<0) {
        perror("socket");
        return 1;
    }
    
    //将套接字绑定到服务器的网络地址上
    if (bind(server_sockfd, (struct sockaddr *)&my_add, sizeof(struct sockaddr))<0) {
        perror("bind");
        return 1;
    }

sockaddr_in 结构体:


    struct sockaddr_in{
        sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
        uint16_t        sin_port;     //16位的端口号
        struct in_addr  sin_addr;     //32位IP地址
        char            sin_zero[8];  //不使用,一般用0填充
    };

(1)、sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
(2)、sin_prot 为端口号。
(3)、sin_addr 是 struct in_addr 结构体类型的变量
(4)、sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。

in_addr 结构体


    struct in_addr{
        in_addr_t  s_addr;  //32位的IP地址
    };

in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换,例如:unsigned long ip = inet_addr("127.0.0.1");

connect() 函数:connect() 函数用来建立连接


    int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); 

各个参数的说明和 bind() 相同。

listen() 函数:通过 listen() 函数可以让套接字进入被动监听状态


    int listen(int sock, int backlog);

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
请求队列:当套接字正在处理客户端的请求时,如果这个时候有新的请求进来,套接字是没法处理的,只能把它放在缓冲区,直到当前请求处理完毕后,再从缓冲区读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区就成为请求队列。缓冲区的大小可以通过listen()函数的backlog参数指定。如果设置为SOMAXCONN,就由系统来决定请求队列长度。
listen() 只是让套接字处于监听状态,并没有接收请求。

accept() 函数:通过 accept() 函数来接收客户端请求


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

sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

write()函数:


    ssize_t write(int fd, const void *buf, size_t nbytes);

fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。

read()函数:


    ssize_t read(int fd, void *buf, size_t nbytes);

fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351