1.什么是Socket?
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),socket就提供了这些操作对应的函数接口。
socket可以看成是用户进程与内核网络协议栈的编程接口。
socket不仅可以用于本机的进程间通信,还可以 用于网络上不同主机的进程间通信。
2.IPv4套接口地址结构
IPV4套妾口地址结构通常也弥为“网际套接字地址结构”,它以 “sockaddr_in”命名,定义在头文件<netinet/in.h>中
struct sockaddr_in{ uint8_t sin_len; //整个sockaddr_in结构体的长度 sa_family_t sin_family; //指定该地址家族,在这里必须设为AF_INET in_port_t sin_port; //端口(2字节) struct in_addr sin_addr; //IPv4的地址(4字节) char sin_zero[8]; //暂不使用,一般将其设置为0 (8字节) }
IPv4套接字一般只需关心3个字段
struct sockaddr_in { sa familytsin_family;/* address family:AF_INET */ in_portt sin_port;/* port in network byte order */ struct in_addr sin_addr;/* internet address */ }; /* Internet address.*/ struct in_addr { uint32_t s_addr;/* address in hetwork byte order }
3.通用地址结构
通用地址结构用来指定与套接字关联的地址
struct sockaddr{ uint8_t sin_len; //整个sockaddr结构体的关度 sa_ family_t sin_family; //指定该地址家族 char sa_data[14]; //由sin_family决定它的形式 }
因为不同的协议地址结构形式可能不一样,通用的可以用于任何协议的接口
一般将IPv4的sockaddr_in强行转换为通用的地址结构sockaddr
4.网络字节序
字节序
大端字节序(Big Endian) :最高有效位(MSB:Most Significant Bit)存储于最低内存地址 处,最低有效位(LSB:Lowest Significant Bit;存储于最高内存地坨处。
小端字节序(l.ittle ndian) :最高有效位(MSB:Most Significant Bit)存储于最高内存地址 处,最低有效位(LSB:Lowest Significant Bit>存储于最低内存地垃处。
主机字节序
不同的主有不同的字节序,如:86为小端字节序,Motorola 6800为 大端字节序,ARM字节序是可配置的。
网络字节序
网络字节序规定为大端字节序
为了将字节序统一,就出现了网络字节序,为大端字节序
编写一个程序测试大小端
#include<stdio.h> int main(void) { unsigned int x=0x12345678; unsigned char *p=(unsigned char*)&x; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); return 0; }
Linux下运行结果:
说明是小端模式
5.字节序的转换函数
uint32_t htonl(uint32_t hostlong); //主机字节序转换为网络字节序 uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); ////网络字节序转换为主机字节序 uint16_t ntohs(uint16_t netshort);
说明:在上述的函数中
h代表 host
n代表 network
s代表 short
I代表 long
程序测试网络字节序
#include<stdio.h> #include<arpa/inet.h> int main() { unsigned int x = 0x12345678; unsigned int y=htonl(x); unsigned char *p=(unsigned char*)&y; printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]); return 0; }
运行结果:
可见,网络字节序会将本地字节序转换为大端模式
6.地址转换函数
#include <netinet/in.h> #include <arpa/inet.h> int inet_ aton(const char *cp,struct in_addr *inp);//将点分十进制转换为网络字节序的结构 in_addr_t inet_addr(const char *cp); //将点分十进制IP地址转换为32位整数 char *inet_ntoa(struct in_addr in); //将网络字节序的结构转换为点分十进制
一般的地址:点分十进制形式,如:192.168.0.100
测试程序:
int main() { unsigned long addr=inet_addr("192.168.0.100"); cout<<ntohl(addr)<<endl; return 0; } //运行结果:3232235620
int main() { unsigned long addr=inet_addr("192.168.0.100"); struct in_addr ipaddr; ipaddr.s_addr=addr; cout<<inet_ntoa(ipaddr)<<endl; return 0; } //运行结果:192.168.0.100
7.套接字类型
常用的三种:
流式套接字(SOCK_STREAM) (TCP协议)
提供面向连接的、可靠的数据传输服务,数据无 差错,无重复的发送,且按发送顺序接收。
数据报式套接字(SOCK_DGRAM) (UDP协议)
提供无连接服务。不提供无错保证,数据可能丢 失或重复,并且接收顺序混乱。
原始套接字(SOCK_RAW)
可以将应用层直接封装成IP层能认识的协议格式
8.TCP客户/服务器模型
9.回射客户/服务器
10.socket的基本函数
socket函数
头文件
<sys/socket.h>
功能:创建一个套接字用于通信
原型
int socket(int domain,int type,int protocol);
参数
domain:指定通信协议族(protocol family)
Name Purpose Man page AF_UNIX, AF_LOCAL Local communication unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK Appletalk ddp(7) AF_PACKET Low level packet interface packet(7)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套 接字SOCK DGRAM,原始套接字SOCK RAW
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. SOCK_RAW Provides raw network protocol access. SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7). SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result. SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.
protocol:协议类型
返回值:成功返回非负整数,它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
bind函数
功能:绑定一个本地地址到套接字
原型
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
参数
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
listen函数
功能:将套接字用于监听进入的连接
原型
int listen (int sockfd,int backlog);
参数
sockfd: socket函数返回的套接字口
backlog: 规定内核为此套接字排队的最大连妾个数口,表示已完成队列和未完成队列的组合,未完成队列表示三次握手还没有成功的条目
返回值:成功返回0,失败返回-1,规定了并发连接的数目
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列
// 调用listen函数后,就成了被动套接字,否则是主动套接字
// 主动套接字:发送连接(connect)
// 被动套接字:接收连接(accept)
accept函数
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);
sockfd:服务器套接字
addr:将返回对等方的套接字地址,相当于把对方的信息填充到结构体中
addrlen:返回对等方的套接字地址长度
返回值:成功返回非负整数,失败返回-1
connect函数
功能:建立一个连接至addr所指定的套接字
原型
int connect(int sockfd;const struct sockaddr*addr;socklen_t addrlen)
参数
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
返回值:成功返回0,失败返回-1
发送/接收函数
服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作,即实现了网咯中不同进程之间的通信.网络I/O操作有下面几组:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。
close()函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
#include <unistd.h> int close(int fd);
close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。
注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
11.简单的服务器客户端程序
回射服务器代码
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<error.h> #include<string.h> #include<iostream> using namespace std; #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ } while (0); int main(void) { //socket int listenfd; //listenfd=socket(PF_INET,SOCK_STREAM,0); if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket"); } //填充地址结构 struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //htonl可以省略,因为INADDR_ANY是全0的 //servaddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&servaddr.sin_addr); //地址复用 int on=1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsocketopt"); } //bind 绑定listenfd和本地地址结构 if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("bind"); } if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("listen"); } // 调用listen函数后,就成了被动套接字,否则是主动套接字 // 主动套接字:发送连接(connect) // 被动套接字:接收连接(accept) //对方的地址 struct sockaddr_in peeraddr; socklen_t peerlen=sizeof(peeraddr); int conn; //已连接套接字(主动) if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0) { ERR_EXIT("accept"); } //连接成功后打印客户端的ip和端口 printf("client: ip=%s | port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); char recvbuf[1024]; while (1) { memset(recvbuf, 0, sizeof(recvbuf)); int ret=read(conn,recvbuf,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,ret); } //关闭套接口 close(conn); close(listenfd); return 0; }
回射客户端代码
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <error.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0); int main() { //socket int sock; if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0) { ERR_EXIT("socket