Linux socket

Linux socket通讯

socket架构

Socket是应用层与TCP/IP协议族通信的中间软件抽象层。

socket方法介绍

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

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIXUnix域)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,只需要了解TCPUDP既可以。

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

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。

addrlen:对应的是地址的长度。

struct sockaddr {
       sa_family_t sa_family;
       char        sa_data[14];//14个字节表示端口和ip地址都在这里面
   }

//网络通讯AF_INET
struct socketaddr_in{
    sa_family_t    sin_family; //地址族(Address Family)
    uint16_t       sin_port;   //16位TCP/UDP 端口号
    struct in_addr sin_addr;   //32位IP 地址
    cahr           sin_zero[8];//不使用
}
//AF_UNIX
struct sockaddr_un{
  	uint8_t sun_len;
  	sa_family_t sun_family; /* AF_LOCAL */
  	char sun_path[104]; /* null-terminated pathname */
};

struct in_addr{
    	In_addr_t      s_addr;  //32位IPv4 地址
}

int listen(int sockfd, int backlog);
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。

int connect(int sockfd, const struct sockaddr*addr, socklen_t addrlen);
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。

int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。

int close(int fd);
关闭函数

ssize_t read(int fd, void buf, size_t count);
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。

ssize_t write(int fd, const void buf, size_t count);
write函数将buf中的字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理

Socket的通信过程方法调用

服务端: socket -> bind -> listen -> accept -> read/write -> close
客户端: socket -> connect -> read/write -> close

socket 网络通信的实战代码

server:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;

    // socket() 打开一个网络通讯端口,如果成功的话,
    // 就像 open() 一样返回一个文件描述符,
    // 应用程序可以像读写文件一样用 read/write 在网络上收发数据。
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    //INADDR_ANY 表示监听0.0.0.0地址,socket只绑定端口,不绑定本主机的某个特定ip,让路由表决定传到哪个ip(0.0.0.0地址表示所有地址、不确定地址、任意地址)(一台主机中如果有多个网卡就有多个ip地址)(路由表应该能知道这个端口正在由哪个ip监听)
    
    //htons()把short型值转成按网络字节顺序排列的short型值
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //htonl()把long型值转成按网络字节顺序排列的long型值
    servaddr.sin_port = htons(SERV_PORT);

    // bind() 的作用是将参数 listenfd 和 servaddr 绑定在一起,
    // 使 listenfd 这个用于网络通讯的文件描述符监听 servaddr 所描述的地址和端口号。
    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    // listen() 声明 listenfd 处于监听状态,
    // 并且最多允许有 20 个客户端处于连接待状态,如果接收到更多的连接请求就忽略。
    listen(listenfd, 20);

    printf("Accepting connections ...\n");
    while (1)
    {
        cliaddr_len = sizeof(cliaddr);
        // 典型的服务器程序可以同时服务于多个客户端,
        // 当有客户端发起连接时,服务器调用的 accept() 返回并接受这个连接,
        // 如果有大量的客户端发起连接而服务器来不及处理,尚未 accept 的客户端就处于连接等待状态。
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

        n = read(connfd, buf, MAXLINE);
        printf("received from %s at PORT %d\n",
               //Ip地址
               inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
               //端口号
               ntohs(cliaddr.sin_port));

        for (i = 0; i < n; i++)
        {
            buf[i] = toupper(buf[i]);
        }

        write(connfd, buf, n);
        close(connfd);
    }
}


client:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;
    char *str;

    if (argc != 2)
    {
        fputs("usage: ./client message\n", stderr);
        exit(1);
    }
    str = argv[1];
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);//htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

	// 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
	// 注意,客户端不是不允许调用 bind(),只是没有必要调用 bind() 固定一个端口号,
	// 服务器也不是必须调用 bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,
	// 每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    write(sockfd, str, strlen(str));

    n = read(sockfd, buf, MAXLINE);
    printf("Response from server:\n");
    write(STDOUT_FILENO, buf, n);
    printf("\n");
    close(sockfd);
    return 0;
}

Unix Socket通信

server:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

#define MAXLINE 80

char *socket_path = "server-socket";

int main()
{
    struct sockaddr_un serun, cliun;
    socklen_t cliun_len;
    int listenfd, connfd, size;
    char buf[MAXLINE];
    int i, n;

    if ((listenfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    memset(&serun, 0, sizeof(serun));
    serun.sun_family = AF_UNIX;
    strncpy(serun.sun_path,socket_path ,
                   sizeof(serun.sun_path) - 1);
    unlink(socket_path);//这个相当于把之前的地址要移除,不然上一个server没有结束,移除会报错already in use
    if (bind(listenfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0) {
        perror("bind error");
        exit(1);
    }
    printf("UNIX domain socket bound\n");

    if (listen(listenfd, 20) < 0) {
        perror("listen error");
        exit(1);
    }
    printf("Accepting connections ...\n");

    while(1) {
        cliun_len = sizeof(cliun);
        if ((connfd = accept(listenfd, (struct sockaddr *)&cliun, &cliun_len)) < 0){
            perror("accept error");
            continue;
        }
        printf("new client connect to server,client sockaddr === %s \n",((struct sockaddr *)&cliun)->sa_data);
        while(1) {
            memset(buf,0,sizeof(buf));
            n = read(connfd, buf, sizeof(buf));
            if (n < 0) {
                perror("read error");
                break;
            } else if(n == 0) {
                printf("EOF\n");
                break;
            }

            printf("received: %s\n", buf);

            for(i = 0; i < n; i++) {
                buf[i] = toupper(buf[i]);
            }
            write(connfd, buf, n);
        }
        close(connfd);
    }
    close(listenfd);
    return 0;
}


client:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#define MAXLINE 80

char *client_path = "client-socket";
char *server_path = "server-socket";

int main() {
    struct  sockaddr_un cliun, serun;
    int len;
    char buf[100];
    int sockfd, n;

    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
        perror("client socket error");
        exit(1);
    }

    memset(&serun, 0, sizeof(serun));
    serun.sun_family = AF_UNIX;
    strncpy(serun.sun_path,server_path ,
                   sizeof(serun.sun_path) - 1);
    if (connect(sockfd, (struct sockaddr *)&serun, sizeof(struct sockaddr_un)) < 0){
        perror("connect error");
        exit(1);
    }
    printf("please input send char:");
    while(fgets(buf, MAXLINE, stdin) != NULL) {
    	write(sockfd, buf, strlen(buf));
    	n = read(sockfd, buf, MAXLINE);
    	if ( n < 0 ) {
        	printf("the other side has been closed.\n");
    	}else {
        	printf("received from server: %s \n",buf);
    	}
    	printf("please input send char:");
    }
    close(sockfd);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值