关于套接字(sockets)入门的一篇好文章
套接字编程(sockets 译文)
作者:Kameswari Chebrolu (印度理工学院坎普尔分校电子工程系) 卡门斯瓦力 车布罗
背景
多路解编
●转变“主机到主机的数据包传输服务”到“程序到程序的数据交流通道”
字节次序
●两类“字节次序”
----网络字节次序:一个数从高位字节开始存在存储器中的最低位地址;
----主机字节次序:一个数从低位字节开始存在存储器中的最低位地址;
----网络堆栈(TCP/IP)需要按网络字节次序存储
●转变
----htons() --> host(主机) to network(网络) Short(Short类型)
----htonl() --> host(主机) to network(网络) Long(Long类型)
----ntohs() --> network(网络) to host(主机) Short(Short类型)
----ntohl() --> network(网络) to host(主机) Long(Long类型)
套接字是什么?
●套接字:应用程序和数据运输层的接口
-应用程序可以通过套接字发送或接受另一个应用程序(本地或远程)的数据
●用Unix术语说,一个套接字是一个文件描述符——一个与公开文件相关联的整数
●套接字类型:Internet Sockets(互联网套接字),unix sockets(Unix套接字),X.25 sockets等
-Internet sockets 由IP地址(4字节)和端口数(2字节)描述
套接字说明书
封装
网络套接字的类型
●Sream Sockets(SOCK_STREAM)
-面向连接的
-依赖TCP提供可靠的两地连接通信
●Datagram Sockets(SOCK_DGRAM) Datagram数据包
-依赖于UDP
-连接是不可靠的
socket()函数 -- 获取文件描述符
●int socket(int domain, int type, int protocol);
-domaim(域)应该被设为PF_INET(PF -> protocol family 协议族)
-type 可以是 SOCK_STREAM or SOCK_DGRAM
-设置protocol为0使套接字基于type选择恰当的协议
-socket() 返回一个套接字描述符,可在之后的系统调用中使用;如果错误函数返回-1
int sockfd;
sockfd = socket(PF_INET,SOCK_STREAM,0);
套接字的结构体
●struct sockaddr:为许多类型的套接字获取其地址信息
struct sockaddr {
unsigned short sa_family; //地址族 AF_XXX
unsigned short sa_data[14]; //14字节的协议地址
}
●struct sockaddr_in:一种使得引用套接字地址更加容易的平行结构体
Struct sockaddr_in {
short int sin_family; //设置 AF_INET
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //网络地址
unsigned char sin_zero[8]; //全设为0
}
●sin_port和sin_addr必须是网络字节次序
处理IP地址
● struct in_addr {
unsigned long s_addr;//32位long类型或4字节
};
● int inet_aton(const char* cp, struct in_addr* inp);
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
inet_aton(“10.0.0.5”, &(my_addr.sin_addr));//功能是将一个字符串IP地址转换为一 个32位的网络序列IP地址
memset(&(my_addr.sin_zero), ’\0’, 8);
-inet-aton() 返回不是0则操作成功;是0则操作失败
●转变二进制IP为字符串: inet_ntoa()
Printf(“%s”, inet_ntoa(my_addr.sin_addr));
bind()- 我在那个端口? (bind 捆绑)
●常在本地计算机上用端口关联套接字
-内核使用端口号将收到的数据包与程序相匹配
●int bind(int sockfd, struct sockaddr* my_addr, int addrlen)
-sockfd 是一个套接字描述符,由socket()函数返回
-my_addr 是sockaddr结构体的指针,包含本机IP地址和端口的信息
-addrlen 要设置为sockaddr结构体的长度 sizeof(struct sockaddr)
-my_addr.sin_port = 0; //任意选择一个没用的端口
-my_addr.sin_addr.s_addr = INADDR_ANY; //使用IP地址
例子
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
my_addr.sin_family = AF_INET; //主机字节次序
my_addr.sin_port = htos(MYPORT); //short类型,网络字节次序
my_addr.sin_addr.s_addr = inet_addr(“172.28.44.57”);
memset(&(my_addr,sin_zero),’\0’,8);//将结构体剩余部分置为0
bind(sockdf, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
/********** 代码需要错误检查。不要忘了********/
connect()函数——你好!
●连接到一个远程主机
●int connect(int sockfd, struct sockaddr* serv_addr, int addrlen)
-sockfd 是一个套接字描述符,由socket()函数返回
-serv_addr 是sockaddr结构体的指针,包含目标IP地址和端口的信息
-addrlen 要设置为sockaddr结构体的长度 sizeof(struct sockaddr)
-返回-1则操作错误
●不需要bind()去捆绑,内核会帮助选择端口
例子
#define DEST_IP “172.28.44.57”
#define DEST_PORT 5000
main(){
int sockfd;
struct sockaddr_in dest_addr; //将持有目的地址
sockfd = socket(PF_INET, SOCK_STREAM, 0);
dest_addr.sin_family = AF_INET;//主机字节次序
dest_addr.sin_port = htons(DEST_PORT);//网络字节次序
dest_addr.sin_addr.s_addr = met_addr(DEST_IP);
memset(&(dest_addr.sin_zero), ’\0’, 8);//将结构体剩余部分置为0
struct connect(sockfd, (struct sockaddr*)&dest_addr,sizeof(struct sockaddr));
}
/**********不要忘记错误检测***********/
listen() —— 请给我回电话
●等待传入的连接
●int listen(int sockfd, int backlog);
-sockfd 是一个套接字描述符,由socket()函数返回
-backlog 是在传入队列中,连接被允许的数量
-listen() 返回-1则操作错误
-在你能够listen()前需要调用bind()
●socket()
●bind()
●listen()
●accept()
accept() —— 感谢你的呼叫!
●accept() 获取端口上你正在listen()的挂起的连接
●int accept(int sockfd, void* addr, int* addrlen);
-sockfd是监听套接字描述符
-收到的连接信息被存在addr,addr是指向本地sockaddr_in结构体的指针
-addrlen 要设置为sockaddr_in结构体的长度 sizeof(struct sockaddr_in)
-accept()返回一个新的套接字文件描述符用于这个接受到的连接,或者返回-1表示操作错误
例子
#include <string.h>
#include <sys/types.h>
#include <sys.socket.h>
#include <netinet/in.h>
#define MYPORT 3490 //端口使用者将连接此
#define BACKLOG 10 //挂起的连接队列将持有
main() {
int sockfd, new_fd; //监听于sockfd,新连接于new_fd
struct sockaddr_in my_addr; //我的地址信息
struct sockaddr_in their_addr;//连接者的地址信息
int sin_size;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
my_addr.sin_family = AF_INET;//主机字节次序
my_addr.sin_port = htons(MYPORT);//short类型,网络字节次序
my_addr.sin_addr.s_addr = INADDR_ANY;//用我的IP自动填充
memset(&(my_addr.sin_zero), ‘\0’, 8);//将结构体剩余部分置为0
//不要忘记错误检查
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);
send()和recv() —— 让我们讲话!
●两个函数是通过流套接字或者建立连接的数据报套接字来沟通的
●int send(int sockfd, const void* msg, int len, int flags);
-sockfd是一个你想传送数据过去的套接字描述符(由socket()返回或从accept()获取)
-msg是指向你想传输的数据的指针
-len是你想传输的数据的字节大小
-将flags暂时设置为0
-sent()返回实际上传输了的字节数(可能比你指定的要小,即小于len);返回-1则操作错误
●int recv(int sockfd, void* buf, int len, int flags)
-sockfd是一个你想读出数据的套接字描述符
-buf将信息读入一个缓冲区
-len是缓冲区的最大长度
-将flags暂时设置为0
-recv()返回实际上读入缓冲区的字节数;返回-1则操作错误
-如果recv()返回0,那么远程端关闭与你的连接
sendto()和recvfrom() —— DGRAM style
●int sendto(int sockfd, const void* msg, int len, int flags, const struct sockaddr* to, int tolen);
-to是一个指向sockaddr结构体的指针,包含目标IP和端口
-tolen是sockaddr的长度 sizeof(struct sockaddr)
●int recvfrom(int sockfd, void* buf, int len, int flags, struct sockaddr* from, int* fromlen);
-from 是一个本地sockaddr结构体的指针,里面会装信息发出端的IP地址和端口
-fromlen是存储在from中的地址的长度
Close() —— 再见!
●int close(int sockfd);
-关闭套接字描述符对应的连接并且释放套接字描述符
-防止多余的发送(send())和接受(recv())
面向连接协议
无连接协议
其他常规
●int getpeername(int sockfd, struct sockaddr* addr, int* addrlen);
-会告诉你同样在连接结束环节的流套接字,并将信息存在addr中
●int gethostname(char* hostname, size_t size);
-会获得正在运行你的程序的计算机名称,并存在hostname中
●struct hostent* gethostbyname(const char* name);
struct hostent {
char* h_name; //主机的名称
char **h_aliases; //主机的替代名称
int h_addrtype; //常为AF_NET
int h_length; //地址的字节长度
char **h_addr_list;//主机的网络地址数组
}
#define h_addr h_addr_list[0]
●代码示例
struct hostent *h;
h = gethostbyname(“www.iitk.ac.in”);
printf(“Host name:%s\n”,h->h-name);
printf(“IP Address:%s\n”,inet_ntoa(*((struct in_addr*)h->h_addr)));
高级主题
●Blocking 阻塞 封锁
●Select 选择
●Handling partial sends 解决局部发送
●Signal handlers 信号处理器
●Threading 线程
总结
●套接字用标准Unix文件描述符帮助应用程序和其他应用程序建立交流
●两类互联网套接字:SOCK_STREAM 和 SOCK_DGRAM
●许多常规帮助简化程序的交流
参考
●书目:
-Unix Network Programming, volumes 1-2 by W.Richard Stevens.
-TCP/IP llustrated, volumes 1-3 by W.Richard Stevens and Gray R.Wright
●网络资源:
-Beej’s Guide to Network Programming
●www.ecst.csuchico.edu/~beej/guide/net/