socket通信
socket是应用层与TCP/IP协议族通信的中间软件抽象层,是一组接口。工作原理如下:
具体过程:服务器端先初始化socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
socket属性
1.创建套接字
套接字的特性由三个属性确定:域(domain), 类型(type)和协议(protocol)。
int socket(int domain, int type, int protocol);
domain:指定socket的类型,一般为AF_INET;
type:是SOCK_STREAM 或SOCK_DGRAM,分别表示TCP连接和UDP连接;
protocol :通常赋值"0"。
socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
2.命名套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针; //addrlen常被设置为sizeof(struct sockaddr)。
返回值:0表示成功;-1表示遇到错误,并将errno置为相应的错误号。
端口号 : 一般不要置为小于1024的值,因为1~1024是保留端口号 ,可使用大于1024中任何一个没有被占用的端口号。
addr的赋值如下:实现自动获得本机IP地址和随机获取一个没有被占用的端口号。
my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */
3.套接字地址结构
套接字地址由结构sockaddr_in来指定,在头文件 <netinet/in.h> 中定义。
第一个结构:
struct sockaddr { unsigned short sa_family; /* 地址族, AF_xxx */ char sa_data[14]; /* 14 字节的协议地址 */ };
sa_family:一般为AF_INET;
sa_data :包含该socket的IP地址和端口号。
第二种结构:
struct sockaddr_in { short int sin_ family; /* 地址族 */ unsigned short int sin_port; /* 端口号 */ struct in_addr sin_addr; /* IP地址 */ unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */ };
sin_family :通常被赋AF_INET;
sin_port和 sin_addr :转换成为网络字节优先顺序 。
sin_zero :用来将sockaddr_in结构填充到与struct sockaddr同样的长度,应该用bzero()或memset()函数将其置为零。
指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,如果一个函数所需参数类型是sockaddr时,可以在函数调用的时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针;或者相反。
IP地址结构 in_addr 被定义为:
struct in_addr{ unsigned long int s_addr; };
4.创建队列
int listen(int sockfd, int backlog);
//backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们
backlog:队列中等待服务的请求数目,系统缺省值为20,常用值是5。
返回值:-1表示遇到错误,errno被置为相应的错误码。
5.阻塞接收
当某个客户端试图与服务器监听的端口连接时,该连接请求将排队等待服务器 accept()它 。
int accept(int sockfd, void *addr, int *addrlen);
//addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);
//addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。
返回值:-1表示错误,并且设置相应的errno值。
6.请求连接
connect()函数用来与远端服务器建立一个TCP连接。
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
//sockfd是目的服务器的sockt描述符;serv_addr是包含目的机IP地址和端口号的指针。
返回值:-1表示遇到错误,并且设置相应的errno值。
7.数据传输
send()和recv()——数据传输
int send(int sockfd, const void *msg, int len, int flags);
//sockfd是你想用来传输数据的socket描述符,msg是一个指向要发送数据的指针,Len是以字节为单位的数据的长度。flags一般情况下置为0。
返回值:返回实际发送出的字节数。当send()返回值与len不匹配时,应该对这种情况进行处理。
int recv(int sockfd,void *buf,int len,unsigned int flags);
//sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。
返回值:返回实际上接收的字节数,或当出现错误时,返回-1并置相应的errno值。
socket使用
例一:server端:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #define MAXLINE 4096 int main( int argc, char** argv ) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[4096]; int n; if( ( listenfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf( "create socket error: %s(errno: %d) ",strerror( errno ),errno ); exit( 0 ); } memset( &servaddr, 0, sizeof( servaddr ) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons( 6666 ); if( bind( listenfd, ( struct sockaddr* )&servaddr, sizeof( servaddr ) ) == -1 ) { printf( "bind socket error: %s(errno: %d) ",strerror( errno ),errno ); exit( 0 ); } if( listen( listenfd, 10 ) == -1 ) { printf( "listen socket error: %s(errno: %d) ",strerror( errno ),errno ); exit( 0 ); } printf( "======waiting for client's request====== " ); while( 1 ) { if( ( connfd = accept( listenfd, ( struct sockaddr* )NULL, NULL ) ) == -1 ) { printf( "accept socket error: %s(errno: %d)",strerror( errno ),errno ); continue; } n = recv( connfd, buff, MAXLINE, 0 ); buff[n] = '