socket(建立通信的端口,并返回引用该端口的文件描述符).
man sockst : https://man7.org/linux/man-pages/man2/socket.2.html
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
int socket(int domain, int type, int protocol);
说明
socket()创建用于通信的端点并返回文件引用该端点的描述符。文件描述符成功调用返回的将是编号最小的文件
该进程当前未打开描述符。
参数
domain(域):
AF_INET,AF_INEF6,AF_LOCAL,AF_ROUTE
函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
表1 domain的值及含义
名称 |
含义 |
PF_UNIX,PF_LOCAL |
本地通信 |
PF_X25 |
ITU-T X25 / ISO-8208协议 |
AF_INET,PF_INET |
IPv4 Internet协议 |
PF_AX25 |
Amateur radio AX.25 |
PF_INET6 |
IPv6 Internet协议 |
PF_ATMPVC |
原始ATM PVC访问 |
PF_IPX |
IPX-Novell协议 |
PF_APPLETALK |
Appletalk |
PF_NETLINK |
内核用户界面设备 |
PF_PACKET |
底层包访问 |
type(类型):
SOCK_STREAM,SOCK_DGRAM,SOCK_PACKET,SOCK_SEQPACKET
函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
表2 type的值及含义
名称 |
含义 |
SOCK_STREAM |
Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输 |
SOCK_DGRAM |
支持UDP连接(无连接状态的消息) |
SOCK_SEQPACKET |
序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 |
SOCK_RAW |
RAW类型,提供原始网络协议访问 |
SOCK_RDM |
提供可靠的数据报文,不过可能数据会有乱序 |
SOCK_PACKET |
这是一个专用类型,不能呢过在通用程序中使用 |
并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
protocol(协议)
IPPROTO_TCP, IPPROTO_UDP, IPPROTO_TIPC;一般为0
函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连
接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内任然没有接受完毕,可以将这个连接人为已经死掉。
SOCK_DGRAM和SOCK_RAW 这个两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接受数据,recvfrom()接受来自制定IP地址的发送方的数据。
SOCK_PACKET是一种专用的数据包,它直接从设备驱动接受数据。
errno
函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
表3 errno的值及含义
值 |
含义 |
EACCES |
没有权限建立制定的domain的type的socket |
EAFNOSUPPORT |
不支持所给的地址类型 |
EINVAL |
不支持此协议或者协议不可用 |
EMFILE |
进程文件表溢出 |
ENFILE |
已经达到系统允许打开的文件数量,打开文件过多 |
ENOBUFS/ENOMEM |
内存不足。socket只有到资源足够或者有进程释放内存 |
EPROTONOSUPPORT |
制定的协议type在domain中不存在 |
示例
建立一个流式套接字:
int sock = socket(AF_INET, SOCK_STREAM, 0);
返回值
成功返回新的文件描述符, 否则返回-1;
bind()( 绑定ip和端口)
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
int bind(int sockfd, const struct sockaddr addr, socketlen_t addrlen );
说明
sockfd是调用socket返回的文件描述符
addr是指向数据结构struct sockaddr 的指针, 它保存你的地址(即端口和IP地址)信息
addrlen设置为sizeof(struct sockaddr)
返回值
出错-1,无错为0
socket 的相关结构体
#include <sys/socket.h>
struct sockaddr{
sa_family sin_family;
char sa_data[14];
};
struct sockaddr_in {
sa_family_t sin_family; //协议族
in_port_t sin_port; //端口
struct in_addr sin_addr; //地址
}
struct in_addr {
unit32_t s_addr;
};
用struct sockaddr_in需要强转成struct sockaddr;
本地与网络的转换
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //本地字节序转换成网络字节序的长整形
uint32_t htons(uint32_t hostshort); //主机字节序转换成网络字节序的短整形
uint32_t ntohl(uint32_t hostlong); //网络字节序转换成本地字节序的长整形
uint32_t ntohs(uint32_t hostshort); //网络字节序转换成本地字节序的短整形
in_addr_t inet_addr (const char *ip);//ip字符串强转成网络字节序的整数型
char *inet_ntoa(struct in_addr in);//将struct in_addr结构体输出为ip字符串
网络字节序和本地字节序不同, 因此要转换.
listen():监听socket
将主动套阶子变被动套阶字;
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
int listen(int sockfd, int backlog);
sockfd是掉用socket()返回的套接字文件描述符.
backlog是在进入队列中允许的连接数目
返回值
出错-1,设errno
accept() :(接收一个套接字中已建立的连接)
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept()用来从sockfd上返回一个新的链接;
1.第一个参数sockfd必须是经由socket(),bind(),listen()函数处理后的socket;
利用系统调用socket()建立的套接字描述符,通过bind()绑定到一个本地地址(一般为服务器的套接字),并且通过listen()一直在监听连
接;
2.第二个参数是一个地址,将保存对端地址到该地址中.
指向struct sockaddr的指针,该结构用通讯层服务器对等套接字的地址(一般为客户端地址)填写,返回地址addr的确切格式由套接字的
地址类别(比如TCP或UDP)决定;若addr为NULL,没有有效地址填写,这种情况下,addrlen也不使用,应该置为NULL;
备注:addr是个指向局部数据结构sockaddr_in的指针,这就是要求接入的信息本地的套接字(地址和指针)。
3.第三个参数是地址长度的地址.
一个值结果参数,调用函数必须初始化为包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值;
备注:addrlen是个局部整形变量,设置为sizeof(struct sockaddr_in)。
说明
accept()系统调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET。它提取出所监听套接字的等待连接队列中第一个连接请求,创建
一个新的套接字,并返回指向该套接字的文件描述符。新建立的套接字不在监听状态,原来所监听的套接字也不受该系统调用的影响。
如果队列中没有等待的连接,套接字也没有被标记为Non-blocking,accept()会阻塞调用函数直到连接出现;如果套接字被标记为Non-blocking,队列中也没有
等待的连接,accept()返回错误EAGAIN或EWOULDBLOCK。
备注:一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;
若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;
若没有,就阻塞等待;
备注:新建立的套接字准备发送send()和接收数据recv()。
返回值
如果成功返回一个新的sockfd,原来的sockfd依然可以用来accept,如果失败,则返回-1;
connect()
客户端主动连接服务端
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
int connet(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:
错误返回-1;否则返回0;
send()发送数据
头文件
#include <sys / types.h>
#include <sys / socket.h>
函数
ssize_t send(int sockfd ,const void * buf ,size_t len ,int flags );
flags可以设置为0;
sendto主要用在UDP通信中
dest_addr为远端要通信的网络地址
返回值
实际读入缓冲的数据的字节数.在出错的时候返回-1;同时设置errno
recv()(接受信息)
头文件
#include <sys / types.h>
#include <sys / socket.h>
函数
ssize_t recv(int sockfd ,void * buf ,size_t len ,int flags) ;
sockfd是要读的套接字描述符
flags可设置为0
返回值
这些调用返回接收到的字节数,如果出错则返回-1。如果发生错误,则将errno设置为指示 错误。
当流套接字对等方执行有序关闭时,返回值将为0(传统的“文件结束”返回)。
各个域(例如UNIX和Internet)中的数据报套接字 域)允许使用零长度数据报。当这样的数据报是
收到,返回值为0。
如果请求的字节数为0,也可能返回值0从流套接字接收的值为0。
close()
#include <unistd.h>
int close(int fd);
getpeername()(获取对端地址)
头文件
#include <sys / socket.h>
函数
int getpeername(int sockfd ,struct sockaddr * addr ,socklen_t * addrlen );
说明
getpeername()在addr指向的缓冲区中返回连接到套接字sockfd的对等方的地址。addrlen参数 应该初始化以指示所指向的空间量地址。返回时,它包含
返回名称的实际大小(用字节)。如果提供的缓冲区太小,名称将被截断。
如果提供的缓冲区也太短,返回的地址将被截断小; 在这种情况下,addrlen返回的值将大于提供给通话。
返回值
成功时,返回零。发生错误时,返回-1,而errno为设置适当。
gethostname(获取本地主机名)
int gethostname(char *hostname, size_t size);
hostname是一个字符数组指针,他将在函数返回时保存主机名.
size是hostname 的字节长度.
返回值: 函数调用成功时返回0,失败返回-1;
setsocketopt()(在套接字上获取和设置选项)
头文件
#include <sys / types.h>
#include <sys / socket.h>
函数
int getsockopt(int sockfd ,int level ,int optname , void * optval ,socklen_t * optlen );
int setsockopt(int sockfd ,int level ,int optname ,const void * optval ,socklen_t optlen );
level:(级别): 指定选项代码的类型。
optname(选项名): 选项名称
optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ }
optlen(选项长度) :optval 的大小
返回值
成功后,标准选项将返回零。错误时,-1. 返回,并正确设置了errno。
一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。
重启地址
int val = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const char*)&val, sizeof(int)) < 0) {
return -1;
}
例子
//服务端的建立sock(),setsockopt(),bind(),listen();
int socket_create(int port) {
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { //int socket(int domain, int type, int protocal);
return -1;
}
int val = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const char*)&val, sizeof(int)) < 0) {
return -1;
}
struct sockaddr_in server;//创建表单名server;
server.sin_family = AF_INET;
server.sin_port = htons(port);//本地字节序转换成网络字节序的短整形
server.sin_addr.s_addr = inet_addr("0.0.0.0");//所有的都要,ip地址转换成网络字符串
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) {
return -1;
}
if (listen(sockfd, 10) < 0) {
return -1;
}
return sockfd;
}
//客户端的建立
int socket_connect(char *ip, int port){
int sockfd;//创建端口名
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
return -1;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) {
return -1;
}
return sockfd;
}