Socket编程

Stella981
• 阅读 836

sockaddr_in与sockaddr

sockaddr是在头文件 /usr/include/bits/socket.h 中定义的,如下:

/* Structure describing a generic socket address.  */
struct sockaddr
{
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];           /* Address data.  */
          
};

而sockaddr_in是在头文件 /usr/include/netinet/in.h 中定义的,如下:

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;             /* Port number.  */
    struct in_addr sin_addr;        /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                                   __SOCKADDR_COMMON_SIZE -
                                   sizeof (in_port_t) -
                                   sizeof (struct in_addr)];

};

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;    
};

二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。 而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。 使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了

NBO,HBO二者转换

NBO

网络字节顺序 (Network Byte Order)

结构体的sin_port和sin_addr都必须是NBO

HBO

本机字节顺序 (Host Byte Order)

一般可视化的数字都是HBO

NBO,HBO二者转换

inet_addr() 将字符串点数格式地址转化成无符号长整型(unsigned long s_addr s_addr;)

inet_aton() 将字符串点数格式地址转化成NBO

inet_ntoa () 将NBO地址转化成字符串点数格式

htons() "Host to Network Short"

htonl() "Host to Network Long"

ntohs() "Network to Host Short"

ntohl() "Network to Host Long"

socket

socket函数,向系统申请一个通信端口

函数原型

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

参数说明

domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、 AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的) 与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。 流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM) 是一种无连接的Socket,对应于无连接的UDP服务应用。

SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议。

OOB: 在所有数据传送前必须使用connect()来建立连接状态。

SOCK_DGRAM: 使用不连续不可靠的数据包连接。

SOCK_SEQPACKET: 提供连续可靠的数据包连接。

SOCK_RAW: 提供原始网络协议存取。

SOCK_RDM: 提供可靠的数据包连接。

SOCK_PACKET: 与网络驱动程序直接通信。

protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。

返回值

如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。

bind

将本端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上

作为client端发包时,bind上的是源地址和源端口

作为server端收包时,是lisen的地址和listen的端口

函数原型

int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len);

参数说明

socket:是一个套接字描述符。

address:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。

address_len:确定address缓冲区的长度。

返回值

如果函数执行成功,返回值为0,否则为SOCKET_ERROR。

listen

函数原型

int listen(int sockfd, int backlog);

参数说明

sockfd: 套接字描述符

int backlog: 队列长度,队列中允许的连接数目,进入的连接在队列中会一直等待直到被接受 accept()

accept

以后send()和 recv()中应该使用accept()产生的新的socket句柄,原有句柄sockfd继续用于listen

函数原型

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

参数说明

sockfd:套接字描述符。

addr:返回连接着的地址

addrlen:接收返回地址的缓冲区长度

返回值

成功返回客户端的文件描述符,失败返回-1。

connect

函数原型

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

参数说明

sockfd:套接字描述符。

addr:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。

addrlen:addr长度

返回值

成功返回文件描述符,失败返回-1。

输入输出操作

read与write

#include <unistd.h>

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

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

readv、writev、preadv与pwritev

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);

ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

参数:filedes要在其上读写的标识符,iov指向iovec结构数组的指针,iovcnt数组元素个数。

返回值:成功返回读写字节数,错误返回-1.

功能:分散读(scatter read),集中写(gather write)。

recv与send

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

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);

参数:前3个参数同read write的3个参数;flags要么是0要么是一些常值的逻辑或;常值及意义参阅头文件。

返回值:成功返回读写字节数,错误返回-1.

功能:比read write多了一个参数flags,可以理解为比read write操作更细化的函数,但仅用于套接字。

recvfrom与sendto

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

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数:前三个参数通write read,flags置为0(后续讲解),from\to 存放对端地址结构,addrlen地址结构长度;

功能:recvfrom接收来自对端from的信息存于buff中;sendto发送信息buff到对端。

备注:(1)注意recvfrom最后一个参数是指向整数值的指针,sendto是一个整数值。(参考值——结果参数)

最后一个参数一般像下面一样给出:

socklen_t addr_len;
addr_len = sizeof(struct sockaddr_in); 
recvfrom(sockfd,buff,sizeof(buff),0,(struct sockaddr *)&addr,&addr_len);

(2)对于UDP,recvfrom返回0是可以接受的;而TCP的read返回0则表示对端关闭连接。

(3)如果不关心对端的协议地址,可以将recvfrom的最后两个参数置为空指针。

(4)recvfrom和sendto都可以用于TCP,但通常不这么做。

recvmsg与sendmsg

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

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

参数:sockfd要对其读写的文件描述符,flags参阅头文件。

返回值:成功返回读写字节数,出错返回-1.

功能:通用的I/O函数,即可以把所有read、readv、recv、recvfrom换成recvmsg;可以把所有write、writev、send、sendto换成sendmsg.

备注:那么是如何做到替换的呢?关键在msghdr结构体,如下,

struct msghdr {
        void         *msg_name;       /* optional address */
        socklen_t     msg_namelen;    /* size of address */
        struct iovec *msg_iov;        /* scatter/gather array */
        size_t        msg_iovlen;     /* # elements in msg_iov */
        void         *msg_control;    /* ancillary data, see below */
        socklen_t     msg_controllen; /* ancillary data buffer len */
        int           msg_flags;      /* flags on received message */
}; 

五组I/O函数比较

函数

任何描述符

仅套接字

单个读写缓冲区

分散集中度写

可选标志

可选对端地址

可选控制信息

read write

y

y

readv writev

y

y

recv send

y

y

y

recvfrom sendto

y

y

y

y

recvmsg sendmsg

y

y

y

y

y

TCP&UDP

TCP情况下(SOCK_STREAM),connect(),bind(),二者都不能少

UDP情况下(SOCK_DGRAM),bind()可以不要,connect()也可以被sendto()代替

UDP不需要BIND (其实连CONNECT也可省略,就剩SENDTO即可),TCP则BIND,CONNECT缺一不可

UDP(SOCK_DGRAM)下,sendto()函数和connect()函数冲突,用sendto,就不能用connect()

UDP sendto发包,尽管配了connect()系统也不报错,但connect函数会使UDP包无法连续发

一般在UDP套接字中使用recvfrom sendto;在TCP套接字中使用read write

UDP到底怎么发包?

或者直接sendto();

或者conncet() + send() 代替 sendto()

Server

#include <stdlib.h>  
#include <pthread.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>

int handle(int point);

int main(int argc, char *argv[])
{
    int sfd, ind;
    struct sockaddr_in addr;
    struct sockaddr_in clent;
    char resv[1024], sendbuf[1024];
    char buf[1024];
    char *myaddr = "127.0.0.1";
    
    int ret; //返回值设置
    socklen_t lent;
    int pid;
    addr.sin_family = AF_INET; //IPv4 Internet protocols
    addr.sin_port = htons(5050); //服务器端口
    addr.sin_addr.s_addr = inet_addr(myaddr); //INADDR_ANY表示本机IP
    
    // 获取socket描述副,IP4ads
    printf("socket start \n");
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0)
    {
        printf("socket error \n");
        return -1;
    
    }
    printf("bind start \n");
    //将套接字与指定端口链接
    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr)) < 0)
    {
        printf("bind err \n");
        return -1;
    
    }
    
    //监听套接字
    printf("listen start \n");
    if (listen(sfd,1024))
    {
        printf("listen error \n");
        return -1;
    
    }
    for (; ; )
    {
        //接受来自客户端的信息
        printf("accept start \n");
        memset(&clent, 0, sizeof(clent));
        lent = sizeof(clent);
        ind = accept(sfd, (struct sockaddr *) &clent, &lent);
        if (ind < 0)
        {
            printf("accept error %d \n", ind);
            return -1;
        
        }
        printf("infor \n");
        printf("clent addr %s porit %d\n", inet_ntop(AF_INET, &clent.sin_addr, buf, sizeof(buf)),
        ntohs(clent.sin_port));
        pid = fork();
        
        if (pid == 0)
        {
            //子进程
            close(sfd);
            handle(ind);
        
        } else if (pid < 0)
        {
            //error
            close(sfd);
        
        } else
        {
            //父进程
        
        }
    
    }
    return EXIT_SUCCESS;

}

int handle(int point) {
    int retn;
    char buf[1024];
    for (; ; )
    {
        retn = read(point, buf, sizeof(buf));
        if (retn < 0)
        {
            printf("read error \n");
            close(point);
            break;
        
        } else if (retn == 0)
        {
            printf("client exit \n");
            break;
        
        }
        printf("client: %s \n", buf);
        if (strcmp("exit", buf) == 0)
        {
            printf("exit \n");
            close(point);
            return 0;
        
        }
    
    } 
    return 0;

}

Client

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int handle(int fd);

int main(int argc, char *argv[])
{
    int nsd;
    char buf[1024];
    
    char *myaddr = "127.0.0.1";
    struct sockaddr_in addr;
    
    printf("welcome to echo client\n");
    nsd = socket(AF_INET, SOCK_STREAM, 0);
    printf("connect start1 \n");
    //bzero(&addr, sizeof(*addr));
    memset(&addr, 0, sizeof(addr));
    printf("connect start2 \n");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5050);
    addr.sin_addr.s_addr = inet_addr(myaddr);
    
    printf("connect start3 \n");
    if (connect(nsd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
    {
        printf("connect error \n");
        return -1;
    
    }
    
    sleep(5);
    printf("handle start \n");
    handle(nsd);
    close(nsd);
    return EXIT_SUCCESS;

}

int handle(int fd) {
    char sendl[1024], rev[1024];
    int retn;
    for (; ; )
    {
        memset(sendl, 0, sizeof(sendl));
        memset(rev, 0, sizeof(rev));
        if (fgets(sendl, 1024, stdin) == NULL)
        {
        break;
        
        }
        printf("write start \n");
        write(fd, sendl, strlen(sendl));
        read(fd, rev, strlen(rev));
    
    }
    return 0;

}

源码下载

SampleC-CPP

参考资料

sockaddr和sockaddr_in的区别

write read;writev readv;recv send;recvfrom sendto;recvmsg sendmsg五组I/O函数汇总

linux socket read 阻塞

C语言实现socket简单通信实例

socket编程——sockaddr_in结构体操作

socket编程——TCP/UDP数据传输

一个简单的生成包和发包的程序

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这