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;
}
源码下载
参考资料
write read;writev readv;recv send;recvfrom sendto;recvmsg sendmsg五组I/O函数汇总