Linux Socket接口使用方法
Linux
内核net/socket.c
定义了一套socket
的操作api
。图1展示了socket
层所处与TCP/IP
协议栈之上和应用层之下。
socket()函数
socket
函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而**socket()**用于创建一个socket
描述符(socket descriptor),它唯一标识一个socket
。这个socket
描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
int socket(int domain, int type, int protocol);
参数:
domain
: 即协议域,又称为协议族(family)。常用的协议组有:
- AF_INET(ipv4地址(32位的)与端口号(16位的)的组合)
- AF_INET6( IPv6 的地址族)
- AF_LOCAL(或称AF_UNIX,Unix域socket)(用一个绝对路径名作为地址)
- AF_ROUTE
type
: 指定socket类型。常用的socket类型有:
- SOCK_STREAM
- SOCK_DGRAM
- SOCK_RAW
- SOCK_PACKET
- SOCK_SEQPACKET等等
protocol
:指定协议。常用的协议有:
- IPPROTO_TCP(TCP传输协议)
- IPPTOTO_UDP(UDP传输协议)
- IPPROTO_SCTP(STCP传输协议)
- IPPROTO_TIPC等(TIPC传输协议)
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
bind()函数
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()
、listen()
时系统会自动随机分配一个端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd
: 即socket
描述字,它是通过socket()
函数创建了,唯一标识一个socket
。bind()
函数就是将给这个描述字绑定一个名字。addr
: 一个const struct sockaddr *
指针,指向要绑定给sockfd
的协议地址。这个地址结构根据地址创建socket
时的地址协议族的不同而不同,如ipv4
对应的是:struct sockaddr_in {short int sin_family; /* 通信类型 */unsigned short int sin_port; /* 端口 */struct in_addr sin_addr; /* Internet 地址 */unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/}; /* Internet address. */ struct in_addr {uint32_t s_addr; /* address in network byte order */ };
注意,原来的
sockaddr
的格式是(16个字节):struct sockaddr {unsigned short sa_family; /* 地址家族, AF_xxx */char sa_data[14]; /*14字节协议地址*/ };
用
sockaddr_in
这个数据结构可以轻松处理套接字地址的基本元素。注意 sin_zero (它被加入到这个结构,并且长度和struct sockaddr
一样) 应该使用函数bzero()
或memset()
来全部置零。 同时,这一重要的字节,一个指向sockaddr_in
结构体的指针也可以被指向结构体sockaddr
并且代替它。这样的话即使socket()
想要的是struct sockaddr *
,你仍然可以使用struct sockaddr_in
,并且在最后转换。ipv6
对应的是:struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ };struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
Unix
域对应的是:#define UNIX_PATH_MAX 108struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
addrlen
:地址的长度。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
网络字节序与主机字节序
主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian
和Little-Endian
的定义如下:
a) Little-Endian
就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian
就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP
首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian
。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket
。
在网络编程中,能够转换两种类型: short (两个字节)和 long (四个字节)。这个函 数对于变量类型 unsigned 也适用。假设你想将 short 从本机字节顺序转 换为网络字节顺序。用 “h” 表示 “本机 (host)”,接着是 “to”,然后用 “n” 表 示 “网络 (network)”,最后用 “s” 表示 “short”: h-to-n-s, 或者 htons() (“Host to Network Short”)。
这里有:
- htons()–“Host to Network Short”
- htonl()–“Host to Network Long”
- ntohs()–“Network to Host Short”
- ntohl()–“Network to Host Long”
为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢?
答案是: sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。
处理IP地址
listenAddr.sin_addr.s_addr = INADDR_ANY;// listenAddr.sin_addr.s_addr = inet_addr(IP);// inet_pton(AF_INET,IP,&listenAddr.sin_addr);
listen()函数
如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket
int listen(int sockfd, int backlog);
参数:
sockfd
::即socket描述字,唯一的id。backlog
:相应socket可以排队的最大连接个数。
socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect()函数
客户端调用connect()发出连接请求,服务器端就会接收到这个请求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd
:socket描述符。addr
:服务器的socket地址。addrlen
:socket地址的长度
再一次,你应该检查 connect() 的返回值–它在错误的时候返回-1,并 设置全局错误变量 errno。 同时,你可能看到,我没有调用 bind()。因为我不在乎本地的端口号。 我只关心我要去那。内核将为我选择一个合适的端口号,而我们所连接的 地方也自动地获得这些信息。一切都不用担心。
客户端通过调用connect函数来建立与TCP服务器的连接。
accept()函数
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd
:服务器的socket描述字。addr
:指向struct sockaddr *
的指针,用于返回客户端的协议地址。addrlen
:协议地址的长度。
注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
send() and recv()函数
两个函数用于流式套接字或者数据报套接字的通讯
send()函数
int send(int sockfd, const void *msg, int len, int flags);
参数:
sockfd
:想要发送数据的套接字描述符。msg
:指向发送的消息数据的指针。flags
:一般设置为0.
send() 返回实际发送的数据的字节数–它可能小于你要求发送的数 目! 注意,有时候你告诉它要发送一堆数据可是它不能处理成功。它只是 发送它可能发送的数据,然后希望你能够发送其它的数据。记住,如果 send() 返回的数据和 len 不匹配,你就应该发送其它的数据。但是这里也 有个好消息:如果你要发送的包很小(小于大约 1K),它可能处理让数据一 次发送完。最后要说得就是,它在错误的时候返回-1,并设置 errno。
recv() 函数
int recv(int sockfd, void *buf, int len, unsigned int flags);
参数:
sockfd
:要读的套接字描述符。buf
:读取数据的缓冲区。len
:缓冲区的最大长度。flags
:一般设置为0.
recv()
返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1, 同时设置 errno。
sendto() 和 recvfrom()函数
既然数据报套接字不是连接到远程主机的,那么在我们发送一个包之 前需要什么信息呢? 不错,是目标地址!
sendto()函数
int sendto(int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr *to, int tolen);
除了另外的两个信息外,其余的和函数 send() 是一样 的。 to 是个指向数据结构 struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。 和函数 send() 类似,sendto() 返回实际发送的字节数(它也可能小于 你想要发送的字节数!),或者在错误的时候返回 -1。
recvfrom() 函数
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
from 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是源机器的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。
recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。
close()函数
关闭文件描述符。
close(sockfd);
shutdown()函数
它将防止套接字上更多的数据的读写。任何在另一端读写套接字的企 图都将返回错误信息。如果你想在如何关闭套接字上有多一点的控制,你可以使用函数 shutdown()。它允许你将一定方向上的通讯或者双向的通讯(就象close()一 样)关闭,你可以使用:
int shutdown(int sockfd, int how);
参数:
sockfd
:是你想要关闭的套接字文件描述符。how
:值是下面的其中之 一:- 0 – 不允许接受
- 1 – 不允许发送
- 2 – 不允许发送和接受(和 close() 一样)
shutdown() 成功时返回 0,失败时返回 -1(同时设置 errno。) 如果在无连接的数据报套接字中使用shutdown(),那么只不过是让 send() 和 recv() 不能使用(记住你在数据报套接字中使用了 connect 后 是可以使用它们的)。
getpeername()函数
函数 getpeername()
告诉你在连接的流式套接字上谁在另外一边。函数是这样的:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
参数:
sockfd
:连接的流式套接字的描述符。addr
:是一个指向结构 struct sockaddr (或者是 struct sockaddr_in) 的指针,它保存着连接的另一边的信息。addrlen
:是一个 int 型的指针,它初始化为 sizeof(struct sockaddr)
函数在错误的时候返回 -1,设置相应的 errno。
一旦你获得它们的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 来打印或者获得更多的信息。但是你不能得到它的帐号
gethostname()函数
它返回你程序所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你的机器的 IP 地址。
#include <unistd.h>
int gethostname(char *hostname, size_t size);
参数:
hostname
:字符串指针,保存返回的主机名。size
:hostname 数组的字节长度。
函数调用成功时返回 0,失败时返回 -1,并设置 errno。
简单的实例
服务器demo代码server.c
:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>#define SERVER_PORT 10086 /*定义用户连接端口*/
#define MAX_CONNECT_NUM 10 /*多少等待连接控制*/
const char *IP = "127.0.0.1"; /*设定本地地址*/void ServerDemo()
{int sockFd; //服务器的文件描述符struct sockaddr_in serverAddr; // 服务器端的ip地址和端口即协议族sockFd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字if (sockFd == -1){printf("create Server Socket error!\n");exit(-1);}// 设置ip地址和协议族及端口serverAddr.sin_family = AF_INET;// serverAddr.sin_addr.s_addr = inet_addr(IP);inet_pton(AF_INET, IP, &serverAddr.sin_addr);serverAddr.sin_port = htons(SERVER_PORT);bzero(&serverAddr.sin_zero, sizeof(serverAddr.sin_zero));// 绑定,服务器需要绑定int bind_ret = bind(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));if (bind_ret == -1){printf("bind error!\n");if (errno == EACCES){exit(1); // 没有权限}else if (errno == EADDRINUSE){exit(2); // 端口被占用}else{exit(errno);}}// 监听int ret = listen(sockFd, MAX_CONNECT_NUM);if (ret == -1){printf("listen error!\n");exit(-1);}// do once// 接收客户端的连接struct sockaddr_in clientAddr;socklen_t clientAddrLen = sizeof(clientAddr);int connectFd = accept(sockFd, (struct sockaddr *)&clientAddr, &clientAddrLen);if (connectFd == -1){printf("connect error!\n");printf("accept failed, errno:%d\n", errno);}else{// 接收一次消息char clientIp[INET_ADDRSTRLEN];printf("connected with %s:%d\n", inet_ntop(AF_INET, &clientAddr.sin_addr, clientIp, INET_ADDRSTRLEN), ntohs(clientAddr.sin_port));char data[1024];recv(connectFd, data, sizeof(data), 0);printf("receive data: %s\n", data);}close(sockFd); // 关闭套接字
}int main()
{ServerDemo();return 0;
}
客户端demo代码client.c
:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/wait.h>
#include <arpa/inet.h>#define PORT 10086 /* 客户机连接远程主机的端口 */
#define MAXDATASIZE 100 /* 每次可以接收的最大字节 */
const char *IP = "127.0.0.1"; /* 设定连接IP地址为本地IP地址 */void ClientDemo()
{int sockFd;// 创建客户端套接字sockFd = socket(AF_INET, SOCK_STREAM, 0);if (sockFd == -1){printf("create client socket error!\n");exit(-1);}// 设定需要连接的服务器的IP地址、地址族以及端口struct sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;// serverAddr.sin_addr.s_addr = inet_addr(IP);inet_pton(AF_INET, IP, &serverAddr.sin_addr);serverAddr.sin_port = htons(PORT);// 连接服务器int ret = connect(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));if (ret == -1){printf("connect failed, errno:%d\n", errno);}else{char data[] = "hello,world!"; // 定义缓冲区send(sockFd, data, sizeof(data), 0); // 发送数据}close(sockFd); // 关闭客户端套接字
}
int main()
{ClientDemo();return 0;
}
分别执行下面的命令:
gcc server.c -o server
gcc client.c -o client
执行
下面是UDP通信的例子(UDP不存在服务器客户端的概念)
udpListen.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>#define MYPORT 4950 /* the port users will be sending to */
#define MAXBUFLEN 100const char *IP = "127.0.0.1";
void UDP_ListenDemo()
{int udpSockFd;struct sockaddr_in listenAddr; // 接收方地址struct sockaddr_in talkAddr; // 发送方的地址udpSockFd = socket(AF_INET, SOCK_DGRAM, 0);if (udpSockFd == -1){printf("create udp sock error!\n");exit(-1);}listenAddr.sin_family = AF_INET;listenAddr.sin_addr.s_addr = INADDR_ANY;// listenAddr.sin_addr.s_addr = inet_addr(IP);// inet_pton(AF_INET,IP,&listenAddr.sin_addr);listenAddr.sin_port = htons(MYPORT);bzero(&listenAddr.sin_zero, sizeof(listenAddr.sin_zero));if (bind(udpSockFd, (struct sockaddr *)&listenAddr, sizeof(listenAddr)) == -1){printf("bind error!\n");exit(-1);}socklen_t talkAddrLen = sizeof(talkAddr);char buf[MAXBUFLEN];int numbytes;numbytes = recvfrom(udpSockFd, buf, MAXBUFLEN, 0, (struct sockaddr *)(&talkAddr), &talkAddrLen);if (numbytes == -1{printf("revefrom error!\n");exit(-1);}printf("got packet from %s\n", inet_ntoa(talkAddr.sin_addr)); // 得到对方的ip地址printf("packet is %d bytes long\n", numbytes);buf[numbytes] = '\0';printf("packet contains \"%s\"\n", buf); //输出接收的内容close(udpSockFd); // 关闭UDP套接字
}
int main()
{UDP_ListenDemo();return 0;
}
udpTalk.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>#define MYPORT 4950
const char *IP = "127.0.0.1";void UDP_TalkDemo()
{int udpTalkFd;struct sockaddr_in listenAddr;struct hostent *he;int numbytes;udpTalkFd = socket(AF_INET, SOCK_DGRAM, 0);if (udpTalkFd == -1){fprintf(stdout, "create udp sock error!\n");exit(-1);}listenAddr.sin_family = AF_INET;listenAddr.sin_addr.s_addr = inet_addr(IP);// inet_pton(AF_INET,IP,&listenAddr.sin_addr);listenAddr.sin_port = htons(MYPORT);bzero(&listenAddr.sin_zero, sizeof(listenAddr.sin_zero));char buf[] = "Hello!";numbytes = sendto(udpTalkFd, buf, sizeof(buf), 0, (struct sockaddr *)&listenAddr, sizeof(listenAddr));if (numbytes == -1){printf("udp send error!\n");exit(-1);}printf("sent %d bytes to %s\n", numbytes, inet_ntoa(listenAddr.sin_addr));close(udpTalkFd);
}int main()
{UDP_TalkDemo();return 0;
}
编写Makefile进行编译
test: udpListen.cgcc udpListen.c -o udpListengcc udpTalk.c -o udpTalk
分别执行:
make test
./udpListen
./udpTalk
结果:
参考:
- c++中Socket编程(入门)
- Linux socket api
- socket接口api的深度探究
Linux Socket接口使用方法相关推荐
- linux点对点聊天室的实现与设计心得,基于Socket接口的Linux与Windows网络聊天室设计与实现...
陈洁 孟晓景 摘要:为了实现Linux与Windows跨平台通信,及时共享信息,构建了一个适用于跨平台的网络聊天室通信程序.先搭建跨平台通信环境,然后使用Socket套接字网络编程接口实现通信.整个系 ...
- pythondockerapi_docker-py 用Python调用Docker接口的方法
众所周知,Docker向外界提供了一个API来管理其中的资源.这个API可以是socket文件形式的(一般也是默认的,在/var/run/docker.sock中),也可以是TCP形式的.以前想要通过 ...
- linux socket bind 内核详解,Socket与系统调用深度分析(示例代码)
1. 什么是系统调用 操作系统通过系统调用为运行于其上的进程提供服务.当用户态进程发起一个系统调用, CPU 将切换到 内核态 并开始执行一个 内核函数 . 内核函数负责响应应用程序的要求,例如操作文 ...
- 对于linux socket与epoll配合相关的一些心得记录
对于linux socket与epoll配合相关的一些心得记录 没有多少高深的东西,全当记录,虽然简单,但是没有做过测试还是挺容易让人糊涂的 int nRecvBuf=32*1024;//设置为32K ...
- linux socket高性能服务器处理框架
这个博客很多东西 http://blog.csdn.net/luozhonghua2014/article/details/37041765 思考一种高性能的服务器处理框架 1.首先需要一个内存池,目 ...
- Linux Socket详解 大全 基础知识
1. Socket基础概念: 1.1:形象类比: Socket和电话网络的概念可以做一个很好的类比: Linux 编程中所说的socket就如同一个端点,类比到电话网中,它就如同一个电话机. 而Soc ...
- linux socket 编程
socket <script type="text/javascript"> </script> <script type="text/j ...
- Linux Socket编程
IP socket 是在其上建立高级Internet 协议的最低级的层:从HTTP到SSL到POP3到Kerberos再到UDP-Time,每种Internet协议都建立在它的基础上.为了实现自定义的 ...
- api有哪些 javasocket_Java Socket编程以及与Linux Socket API关系
Socket 编程(基于Linux) Socket独立于具体协议的网络编程接口,在ISO模型中,主要位于会话层和传输层之间:在通用的计算机网络五层模型中,主要位于应用层和传输层之间. Linux So ...
最新文章
- PHP开发之递归算法的三种实现方法
- 微信小程序设置云函数使用的环境
- UVA 1451 Average 数形结合
- lsof/netstat命令的一个重要作用: 根据进程查端口, 根据端口查进程
- IE6的重定向页面无法跳转解决
- WIN10网络共享文件夹实战
- 如何更改计算机屏幕分辨率,如何修改电脑默认屏幕分辨率
- 龙之谷私服源码+搭建教程
- Tecplot 自定义色谱颜色
- 文献阅读【RNA-seq数据归一化】
- webpack 环境变量
- python流程控制工具
- 公众号h5获取手机号权限_微信公众号h5获取用户openId的方法和步骤
- ExtJS初级教程之ExtJS Tree(三)
- kubernetes1.5.2版本 yum install 方式安装部署 认证授权机制 安全模式 完整版
- 解决Discus!x3.1论坛安装插件提示无法运行通过zend加密应用
- 机器学习准备数据时如何避免数据泄漏
- jQuery Mobile-页面跳转
- 多种方法清理电脑内存,解决电脑卡问题
- B014 - ADC0809数字电压表可切换量程
热门文章
- 电脑上怎么绘制流程图?三分钟快速绘制流程图的秘诀
- 首位文博虚拟宣推官“文夭夭”上岗
- 若语句char a = ‘\72‘; 则变量a包含几个字符?‘\72‘是否在ASCII值的范围之内?
- 软件测试之测试用例的设计
- Python9-前端基础知识-day47
- 简单聊聊Long Short Term Memory Network (LSTM)和 Gated Recurrent Unit (GRU)两种强大的RNN变体
- 关于支付系统(支付通道,支付接口)
- C++描述 LeetCode 5677. 统计同构子字符串的数目
- 利用cobaltstrike加sqlmap拿下一个网站并提权
- leetcode之Isomorphic strings