文档章节

Socket编程-C

yxmsw2007
 yxmsw2007
发布于 2017/08/11 22:56
字数 2738
阅读 7
收藏 0

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数据传输

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

© 著作权归作者所有

上一篇: Thread编程-C
下一篇: VIM常用命令
yxmsw2007

yxmsw2007

粉丝 4
博文 42
码字总数 40160
作品 0
深圳
程序员
私信 提问
Android NDK Socket(POSIX Socket Api)编程

socket简介 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。 tcpsocket和udpsocket的具体实现 讲了这么久,终于要开始讲socket的具体实现了,iOS提供了Socket网络编程的接...

IamOkay
2016/04/10
670
0
柳大的Linux讲义·基础篇(4)网络编程基础

柳大的Linux游记·基础篇(4)网络编程基础 Author: 柳大·Poechant Blog: Blog.CSDN.net/Poechant Email:zhongchao.usytc#gmail.com (#->@) Date:March 11th, 2012 Copyright © 柳大·P......

晨曦之光
2012/04/24
92
0
python的Socket编程基础

下面一些是python网络编程基础知识,很少在项目中直接使用,都是用的twisted,gevent,tornado等网络框架.但是学习基础知识可以弄懂socket流程. python的socket模块的网络编程步骤和linux c基本一...

flyking
2013/10/23
259
0
Go基础编程:Socket编程

1 什么是Socket Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数...

tennysonsky
2018/01/18
0
0
有了WCF,Socket是否已人老珠黄?

1. Socket相关背景   Socket,中文译为“套接字”,最早在UNIX中引入并得到广泛应用,后来微软在设计Windows时引入了UNIX中的这个概念和相应的设计理念,并针对Windows的特性略作调整,形成...

付翔
2013/01/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

规则引擎

解决问题 版本迭代速度更不上业务变化,但是若多个业务同时变化,除了为每个业务设计专属配置项也不利于操作。就想服务接口单纯化,将复杂多变的业务逻辑交给规则引擎,让用户在web端或cs端自...

无极之岚
26分钟前
4
0
OSChina 周三乱弹 —— 欢迎你来做产品经理

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @巴拉迪维 :10多次劲歌金曲获奖,更多叱咤歌坛排名,黎明才应该是四大天王之首,只可惜拍的电影太少。单曲循环一个多月的歌,力荐 《无名份的...

小小编辑
今天
215
9
500行代码,教你用python写个微信飞机大战

这几天在重温微信小游戏的飞机大战,玩着玩着就在思考人生了,这飞机大战怎么就可以做的那么好,操作简单,简单上手。 帮助蹲厕族、YP族、饭圈女孩在无聊之余可以有一样东西让他们振作起来!...

上海小胖
今天
10
0
关于AsyncTask的onPostExcute方法是否会在Activity重建过程中调用的问题

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/XG1057415595/article/details/86774575 假设下面一种情况...

shzwork
今天
7
0
object 类中有哪些方法?

getClass(): 获取运行时类的对象 equals():判断其他对象是否与此对象相等 hashcode():返回该对象的哈希码值 toString():返回该对象的字符串表示 clone(): 创建并返此对象的一个副本 wait...

happywe
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部