相信做过通信的人对于套接字一点都不陌生,套接字通信主要用于网络通信,不同计算机之间进行通信。其具有相当强大的功能,套接字通信说到底还是两个进程之间的通信,很多人传统的观念认为使用套接字,TCP/UDP就涉及到网络,涉及到不同的计算机之间,但是套接字不止这种使用方式。它同样可以作为IPC通信完成本机之间两个进程的通信。实现这种方法需要回环网卡的支持,(也就是lo网卡127.0.0.1)。
在实现通信之前,需要知道程序设计的流程:(传统的经典流程图如下):
套接字:
套接字是计算机操作系统为应用于TCP/IP协议交互提供的一种通用接口。套接字代表了通信的端点。也就是说是数据通过TCP/IP协议在电缆上传输后进入目的主机的入口。
套接字作为通信端点的抽象,与应用程序要使用文件描述符访问文件一样,访问套接字也需要使用套接字描述符。
-
#include <sys/socket.h>
-
int socket(int domain, int type, int protocol);
-
成功返回文件(套接字)描述符,若出错则返回-1;
参数说明:
1、int domain :确定通信的特性,表示各个域的常数都是以AF_开头,表示地址族(address family):
AF_INET IPv4因特网域
AF_INET6 IPv6因特网域
AF_UNIX UNIX域
AF_UNSPEC 未指定
2、type:确定套接字的类型其有下列4中类型:
SOCK_DGRAM 长度固定的、无连接的不可靠的报文传输(基于UDP)
SOCK_RAW IP协议的数据报接口(原始套接字)
SOCK_SEQPACKET 长度固定、有序、可靠的面向连接报文传递
SOCK_STREAM 有序、可靠、双向的面向连接字节流(TCP)
3、protocol通常为零,表示按给定的域和套接字类型选择默认协议。AF_INET中SOCK_STREAM的默认协议时TCP,SOCK_DGRAM默认的协议时UDP。
在建立连接之前,还需要做一些准备工作;字节序和地址格式结构填充。
运行在同一台电脑上的进程相互通信时,一般不用考虑字节的顺序,字节序是一个处理器架构的特性,用于指示像整数这样的大数据类型的内部字节顺序。
大端(big-endian)字节序:最大字节地址对应于数字最低有效字节(LSB)上。小端(little-endian)字节序则相反。
处理器架构 字节序
Intel Pentium 小端
PowerPC 大端
Sun SPARC 大端
测试处理器是什么字节序:
union
{
int number;
char s;
}test;
int main(void)
{
test.number=0x01000010;
if(test.s == 0x01)
printf("big-endian\n");
else
printf("little-endian\n");
return 0;
}
网络传输的数据顺序一定要是统一的,网络字节序采用大端存储(big-endian)字节序。
#include 有的老版本的定义在#include
uint32_t htonl(uint32_t,hostint32);
返回值:以网络字节序表示的32位整形数;
uint16_t htons(uint16_t,hostint16);
返回值:以网络字节序表示的16位整形数;
uint32_t ntohl(uint32_t,hostint32);
返回值:以主机字节序表示的32位整形数;
uint16_t ntohs(uint16_t,hostint16);
返回值:以主机字节序表示的16位整形数;
h:表示host(主机)字节序; l:表示long长整形数;
n:表示net(网络)字节序; s:表示short整形数;
地址格式:涉及到了两个结构体
#include
struct sockadd_in {
sa_family_t sin_family; //协议族
in_port_t sin_port; //端口号
struct in_addr sin_addr; //IP4地址
};
struct in_addr {
in_addr_t s_addr; //IP4地址
}
-
struct sockaddr_in;
-
bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0
-
server_addr.sin_family=AF_INET; // Internet
-
server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // (将本机器上的long数据转化为网络上的long数据)和任何主机通信 //INADDR_ANY 表示可以接收任意IP地址的数据,即绑定到所有的IP
-
//server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
-
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
服务器端程序设计:
-
#include <sys/socket.h>
-
-
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
-
-
返回值:成功返回0,失败返回-1;
注意:
1、在进程运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址;
2、地址必须和创建套接字时的地址族所支持的格式相匹配;
3、端口号必须比小于1024,除非是超级用户;
4、一般只有套接字端点能够与地址绑定;
对于因特网域,如果指定IP地址为INADDR_ANY,套接字端点可以被绑定到所有的系统网络接口。也就是说可以收到这个系统所安装的所有网卡的数据包。
监听listen():
-
#include <sys/socket.h>
-
-
int listen(int sockfd, int backlog);
-
-
返回值:成功为0,失败为-1;
参数:backlog提供了一个提示,用于表示该进程所要入列的连接请求数量。一旦队列满,系统会拒绝多余的连接请求,一旦系统调用了listen(),套接字就能接收连接请求。使用函数access()获得连接请求并建立连接。
-
#include <sys/socket.h>
-
-
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
-
-
返回值:成功返回文件描述,失败返回-1;
函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个链接,而是继续保持可用状态并接受其他链接请求。
到这里服务器程序链接就建立好了,接下来就可以调用read(),write()函数进行读写操作;
客户端相关函数:
-
#include <sys/socket.h>
-
-
int connect(int sockfd, const struct *addr, socklen_t len);
-
-
返回值:成功返回0,错误返回-1;
参考程序:先运行server.c程序,再运行client.c程序,连接成功server将打印出client的ip地址。(./client 127.0.0.1)
服务器端程序:
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <netdb.h>
-
#include <sys/types.h>
-
#include <netinet/in.h>
-
#include <sys/socket.h>
-
-
#define portnumber 3333
-
-
int main(int argc, char *argv[])
-
{
-
int sockfd,new_fd;
-
struct sockaddr_in server_addr;
-
struct sockaddr_in client_addr;
-
int sin_size;
-
int nbytes;
-
char buffer[1024];
-
-
-
/* 服务器端开始建立sockfd描述符 */
-
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:IPV4;SOCK_STREAM:TCP
-
{
-
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
/* 服务器端填充 sockaddr结构 */
-
bzero(&server_addr,sizeof(struct sockaddr_in)); // 初始化,置0
-
server_addr.sin_family=AF_INET; // Internet
-
server_addr.sin_addr.s_addr=htonl(INADDR_ANY); // (将本机器上的long数据转化为网络上的long数据)和任何主机通信 //INADDR_ANY 表示可以接收任意IP地址的数据,即绑定到所有的IP
-
//server_addr.sin_addr.s_addr=inet_addr("192.168.1.1"); //用于绑定到一个固定IP,inet_addr用于把数字加格式的ip转化为整形ip
-
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
-
-
/* 捆绑sockfd描述符到IP地址 */
-
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
-
{
-
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
/* 设置允许连接的最大客户端数 */
-
if(listen(sockfd,5)==-1)
-
{
-
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
-
while(1)
-
{
-
/* 服务器阻塞,直到客户程序建立连接 */
-
sin_size=sizeof(struct sockaddr_in);
-
if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
-
{
-
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
-
exit(1);
-
}
-
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); // 将网络地址转换成.字符串
-
-
if((nbytes=read(new_fd,buffer,1024))==-1)
-
{
-
fprintf(stderr,"Read Error:%s\n",strerror(errno));
-
exit(1);
-
}
-
buffer[nbytes]='\0';
-
printf("Server received %s\n",buffer);
-
-
/* 这个通讯已经结束 */
-
close(new_fd);
-
/* 循环下一个 */
-
}
-
-
/* 结束通讯 */
-
close(sockfd);
-
exit(0);
-
}
客户端程序:
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <errno.h>
-
#include <string.h>
-
#include <netdb.h>
-
#include <sys/types.h>
-
#include <netinet/in.h>
-
#include <sys/socket.h>
-
-
#define portnumber 3333
-
-
int main(int argc, char *argv[])
-
{
-
int sockfd;
-
char buffer[1024];
-
struct sockaddr_in server_addr;
-
struct hostent *host;
-
-
/* 使用hostname查询host 名字 */
-
if(argc!=2)
-
{
-
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
-
exit(1);
-
}
-
-
if((host=gethostbyname(argv[1]))==NULL)
-
{
-
fprintf(stderr,"Gethostname error\n");
-
exit(1);
-
}
-
-
/* 客户程序开始建立 sockfd描述符 */
-
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) // AF_INET:Internet;SOCK_STREAM:TCP
-
{
-
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 客户程序填充服务端的资料 */
-
bzero(&server_addr,sizeof(server_addr)); // 初始化,置0
-
server_addr.sin_family=AF_INET; // IPV4
-
server_addr.sin_port=htons(portnumber); // (将本机器上的short数据转化为网络上的short数据)端口号
-
server_addr.sin_addr=*((struct in_addr *)host->h_addr); // IP地址
-
-
/* 客户程序发起连接请求 */
-
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
-
{
-
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
-
exit(1);
-
}
-
-
/* 连接成功了 */
-
printf("Please input char:\n");
-
-
/* 发送数据 */
-
fgets(buffer,1024,stdin);
-
write(sockfd,buffer,strlen(buffer));
-
-
/* 结束通讯 */
-
close(sockfd);
-
exit(0);
-
}
使用套接字进行数据处理有两种基本模式:同步和异步。
同步模式:
同步模式的特点是在通过Socket进行连接、接收、发送数据时,客户机和服务器在接收到对方响应前会处于阻塞状态,即一直等到收到对方请求进才继续执行下面的语句。可见,同步模式只适用于数据处理不太多的场合。当程序执行的任务很多时,长时间的等待可能会让用户无法忍受。
异步模式:
异步模式的特点是在通过Socket进行连接、接收、发送操作时,客户机或服务器不会处于阻塞方式,而是利用callback机制进行连接、接收、发送处理,这样就可以在调用发送或接收的方法后直接返回,并继续执行下面的程序。可见,异步套接字特别适用于进行大量数据处理的场合。
当遇到在有连接请求的时候就行连接,改变一些状态量,没有连接请求来的时候希望程序跳过accept继续往下执行就需要用到异步模式,实现方法就是改变最初套接字的模式;
-
#include <unistd.h>
-
#include <fcntl.h>
-
int flag;
-
if((flag = fcntl(sockfd,F_GETFL,0) == -1)
-
flag = 0;
-
fcntl(sockfd,F_SETFL,flag | O_NONBLOCK);
具体关于fcntl操作文件描述的函数可以参考博客:
阅读(1947) | 评论(0) | 转发(0) |