Socket编程
1、socket中的数据结构和数据处理
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
struct sockaddr_in {
short int sin_family; // Address family
unsigned short int sin_port; // Port number
struct in_addr sin_addr; // Internet address
unsigned char sin_zero[8]; // Same size as struct sockaddr
};
// Internet address (a structure for historical reasons)
struct in_addr {
unsigned long s_addr; // that’s a 32-bit long, or 4 bytes
};
这 两个结构的作用是一样的,通常多数都是用sockaddr_in,他比较直观。但是socket API中都是用的sockaddr,所以使用这个数据结构需要做强制类型转换。第四个域sin_zero使用menset()将其设置为0就好了。还有就 是,sin_port 和 sin_addr必须是网络字节顺序,使用htons()。
the sin_family field is only used by the kernel to determine what type of address the structure contains, so it must be in Host Byte Order.
因为sin_family只是给kernel用来决定结构所包含的地址的类型,所以它必须是主机字节顺序的。
1.1、用于处理字节顺序的函数:
• htonl() – "Host to Network Long"
• ntohs() – "Network to Host Short"
• ntohl() – "Network to Host Long"
1.2、IP地址及其处理
首先定义一个struct sockadd_in:
假定我的ip是10.12.110.57,那么可以用inet_addr()将ip地址填入该结构:
还有,inet_addr()处理后的就已经是网络字节顺序了,所以不需要处理字节顺序。
inet_addr()返回-1表示出错,注意-1正好是二进制数对应的255.255.255.255!要作错误检查。
相对于inet_addr(),还有个更好用的提供类似功能的接口inet_aton()(“ation”表示“asiic to network):
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
看一个简单的例子:
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
inet_aton("10.12.110.57", &(my_addr.sin_addr));
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
不同一般的socket api,inet_aton()返回非0表示成功,返回0表示失败,而且不是所有平台都实现这个接口。
类似地,inet_aton()是将字符串的IP地址转换成二进制表示,inet_ntoa()二进制表示转换成字符串的IP(数字以点隔开)。
注意;inet_ntoa()需要一个struct in_addr作为参数,并且返回一个指针,可以使用strcpy()拷贝IP地址。
2、socket API
2.1、socket()–Get the File Descriptor!
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
@domain:AF_INET
@type:SOCK_STREAM|SOCK_DGRAM
@protocol:设置为0,让socket()基于@type选择协议,当然还有更好的选择协议的方法,man getprotobyname()。
返回一个文件描述符,-1表示错误,同时设置全局变量errno。
2.2、bind()–What port am I on?
一旦有了一个socket,就可以将socket和本地机器上的端口关联。端口是kernel用来将接收到的包和某个进程的socket文件描述符匹配。
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
@sockfd:上面得到的socket,也就是要关联端口的socket
@my_addr:port、IP
@addrlen:sizeof(struct sockaddr)
看个例子:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490
main()
{
struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
// don’t forget your error checking for bind():
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
.
.
.
IP和port可以通过下面的动作bind()自动获取:
my_addr.sin_addr.s_addr = INADDR_ANY; // use my IP address
bind()同样也是返回-1表示错误,同时设置errno。
注意:port的范围,1024以后是系统分配了的,所以可以在1024--65535之间取值。
2.3、connect()
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
@sockfd: socket() returned
@serv_addr: 目标主机,即你要连的服务器
@addrlen:sizeof(struct sockaddr)
看个例子:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEST_IP "10.12.110.57"
#define DEST_PORT 23
main()
{
struct sockaddr_in dest_addr; // will hold the destination addr
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
dest_addr.sin_family = AF_INET; // host byte order
dest_addr.sin_port = htons(DEST_PORT); // short, network byte order
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
memset(&(dest_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
// don’t forget to error check the connect()!
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
.
.
.
同样的,返回-1表示错误,同时设置errno。
2.4、listen()监听连接的到来
@socket:得自socket()
@backlog:请求队列的长度
同样地,-1表示错误,同时设置errno。
2.5、accept()接受连接请求
在accept之前,所有的连接请求都在请求队列中等待被accepted。accept()将返回一个新的socket文件描述符用于处理每个连接,这样将新产生一些socket文件描述符,同时原来的那个socket文件描述符仍然监听端口。
int accept(int sockfd, void *addr, int *addrlen);
@sockfs:监听端口的那个socket文件描述符
@addr:一个指向本地的struct sockaddr_in的指针,这个用来存放远程连接的信息
@addrlen:sizeof(struct sockaddr_in)
看个例子:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
main()
{
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector’s address information
int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
memset(&(my_addr.sin_zero), ’\0’, 8); // zero the rest of the struct
// don’t forget your error checking for these calls:
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
.
.
.
注意:通信过程中数据的接收和发送都是通过后来accept()返回的socket文件描述符来传送的。
4.6、send() recv()数据的收发
这两个接口是用于stream socket或者有连接的datagram socket,如果要无连接的datagram通信,需使用sendto() 和 recvfrom()。
原型:
@sockfd:用于通信的socket文件描述符
@msg:消息,通信的内容
@flags:0,更多信息查看man send
返回发送的字节数,有可能小于len,-1表示错误,并且errno。
@sockfd:用于接口的socket文件描述符
@buf:用于存放接口的消息
@flags:0,更多信息查看man recv
返回接收到的字节数,-1表示出错,并且errno,如果返回的是0表示远程连接已经关闭。
4.7、sendto() recvfrom() DGRAM格式的通信
相对于有连接的通信,无连接的通信需要提供更多的关于通信双方的信息,所以sendto()和recvfrom()都多2个参数。
原型:
const struct sockaddr *to, int tolen);
@sockfd:
@msg:
@flags:
这三个和send()是一样的
@to:一个指针,指向struct sockaddr,目标的IP和PORT
@tolen:sizeof(struct sockadr)
struct sockaddr *from, int *fromlen);
@sockfd:
@buf:
@flags:
这三个和recv()是一样的
@from:
@fromlen:
4.8、close() shutdown()
int shutdown(int sockfd, int how);
@sockfd:想要断开的那个socket
@how:断开的方式
• 0 – Further receives are disallowed
• 1 – Further sends are disallowed
• 2 – Further sends and receives are disallowed (like close())
返回0表示成功,-1表示出错
注意:
shutdown不会关闭文件描述符,它只是改变文件描述符的可用性。要释放文件描述符,需调用close()。
4.9、getpeername()
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
4.10 gethostname()
int gethostname(char *hostname, size_t size);