Linux 套接字编程
一、网络层结构 网络层应用程序通过BSD套接字进行数据传输,它下面是INET套接字层,管理TCP和UDP协议
BSD套接字接口是BSD的进程间通信方式,不仅支持各种形式的网络应用,而且是进程间通信的机制
1)stream (数据流)提供两个方向的数据传输,保证传输过程数据不丢失、破坏或重复。对应TCP协议支持
2)datagram(数据报)支持两个方向上的数据传输,不提供消息到达保证,由UDP支持,一般是面向事务处理的
3)Raw原始套接字,允许进程直接访问底层协议
4)Reliable Delivered Message 可靠数据报传输
5)Sequenced Packets 顺序数据报,像数据流但是数据包大小确定
6)Packet 允许进程在设备层直接访问Packet
二.套接字地址
1.通用套接字地址结构
#include<sys/socket.h>
struct socketaddr
{
unsigned short int sa_family;// 地址类型,AF_XXX如TCP/IP地址类型为AF_INET
unsigned char sa_data[14];//14字节的协议地址
};
2.TCP/IP套接字地址结构sockaddr_in
#include<netinet/in.h>
struct in_addr
{
int_addr_t s_addr;
};
struct sockaddr_in
{
_SOCKADDR_COMMON(sin__;
in_port_t sin_port;/**端口号**/ 网络字节存储
struct in_addr sin_addr;/*Internet 地址*/网络字节存储
/**填充部分 未用**/通常是设为0
unsigned char sin_zero[sizeof(struct sockaddr)-_SOCKADDR_COMMON_SIZE-sizeof(int_port_t)-sizeof(struct in_addr)];
};
地址引用:servaddr.sin_addr 引用的结构类型 struct in_addr数据
servaddr.sin_addr.s_addr 引用的是整形数据
2.设置IP
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int inet_aton(char *cp,struct in_arrd *inp);将字符形式的IP转换成二进制IP,存储在inp中,成功返回1,否则返0
unsigned long int inet_addr(char *cp);不能处理广播,错误返回长岭INADDR_NONE
char * inet_ntoa(strunct in_addr in); 将32位二进制形式的IP地址转换成数字点形式的IP,结果在函数中返回
3.字节顺序
1)大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
2)小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
利用union检测系统的字节存储模式
union 型数据所占的空间等于其最大的成员所占的空间。对union 型的成员的存取都是相对于该联合体基地址的偏移量为0 处开始,也就是联合体的访问不论对哪个变量的存取都是从union 的首地址位置开始。
int checkSystem( )
{
union check
{
int i;
char ch;
} c;
c.i = 1;
return (c.ch ==1);
}
而网络协议中数据采用统一的网络字节顺序,全部采用big-endian方式
字节转换函数
#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);//主机字节顺序转换成网络字节顺序
unsigned short int htons(unsigned sjort int hostshort);
unsigned long int ntohl(unsigned long int netlong);//网络字节顺序转换成主机字节顺序
unsigned short int ntohs(unsigned sjort int netshort)
4.字节处理函数
#include<strings.h>
void bzero(void *s,int n);将s制定内存的前n个字节设成0。套接字地址清零bzero(&servaddr,sizeof(servaddr))
void bcopy(void *src,void *dest,int n) ;从src复制n个字节内容到dest指定内存
int bcmp(void *s1,void *s2,int n)比较s1和s2的前n个字符,相同返回0,否则返回非0
#include<string.h>
void *memset(void *s,int c,size_t n);s指定内存区域前n个字节设成参数c
void *memcpy(void *dest,void *src,size_t n);
int memcmp(void *s1,void *s2,size_t n);比较s1和s2指定区域的前n个字节内容,相同返回0,否则返回非0
三、套接字函数
1.socket函数 创建一个套接字描述符,成功返回套接字描述符,否则返回-1
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
domain:要创建的套接字协议族
AF_UNIX UNIX域协议族,本机进程通信使用
AF_INET Internet 协议族
AF_ISO ISO 协议族
type:套接字类型
SOCK_STREAM 流套接字TCP
SOCK_DGRAM 数据报套接字
SOCK_RAM 原始套接字
protocol:通常设为0 默认
2.connet连接
#include<sys/types.h>
#include<socket.h>
int connect(sockfd,struct sockaddr * servaddr,int addrlen);servaddr 远程服务器的套接字地址,addrlen套接字地址长度,成功返回0,失败返回-1
3.bind函数
#include<sys/types.h>
#include<socket.h>
int bind(int sockfd,struct sockaddr *myaddr,int addrlen);myaddr 本地地址,addrlen套接字地址结构的长度,bind成功返回0否则返回-1
4.listen将一个套接字装换成倾听套接字
#include<sys/types.h>
#include<socket.h>
int listen(int sockfd,int backlog)sockfd 指定要转换的套接字描述符,backlog设置请求队列的最大长度,成功返回0,失败返回-1
要建立一个倾听套接字,就必须调用socket创建主动套接字,调用bind绑定服务器套接字地址,调用listen进行转换
5.accept
#include<sys/types.h>
#include<socket.h>
int accept(int sockfd,struct sockaddr *addr,int*addrlen) 从倾听套接字的完成队列中接受一个连接
函数返回一个新的套接字描述符标志接受的连接;addr指向结构变量中客户机的地址;参数addrlen指向整形变量中存储客户机地址的长度
服务器程序:
/***ex8-2server.c***/
#include<time.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<string.h>
#define LISTENQ 5
#define MAXLINE 512
int main()
{
int listenfd,connfd;
socklen_t len;
struct sockaddr_in servaddr,cliaddr;
char buff[MAXLINE];
time_t ticks;
listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0)
{
printf("Socket created failed.\n");
return -1;
}
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(6666);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
{
printf("bind failed.\n");
return -1;
}
printf("listening....\n");
listen(listenfd,LISTENQ);
while(1)
{
len=sizeof(cliaddr);
connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&len);
printf("connet from %s,port %d\n",inet_ntoa(cliaddr.sin_addr.s_addr),ntohs(cliaddr.sin_port));
ticks=times(NULL);
sprintf(buff,"%.24s\r\n",ctime(&ticks));
write(connfd,buff,strlen(buff));
close(connfd);
}
}
客户端程序
/***ex8-2client.c**/
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#define MAXBUFFERSIZE 256
#define PORT 6666
#define HOST_ADDR "127.0.0.1"
int main(int argc,char *argv[])
{
int sockfd,n;
char revbuff[MAXBUFFERSIZE];
struct sockaddr_in servaddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
printf("Socket created failed.\n");
return -1;
}
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(6666);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
{
printf("Connect failed.\n");
return -1;
}
while((n=read(sockfd,revbuff,MAXBUFFERSIZE))>0)
{
revbuff[n]=0;
fputs(revbuff,stdout);
}
if(n<0)
{
printf("read failed.\n");
return -2;
}
return 0;
}
6.getsockname
int getsockname(int socket,(socket sockaddr*)address,socklen_t *address_len) 获取由socket给出的套接字的本地绑定名,成功返回0,并 将地址存在address,超出部分被截断,长度存储在address_len
失败返回-1
/**ex8-3.c***/
#include<sys/socket.h>
#include<sys/time.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdio.h>
int main()
{
int sockfd;
socklen_t len;
struct sockaddr_in addr,raddr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
printf("createsocketfailed!\n");
return -1;
}
addr.sin_family=AF_INET;
addr.sin_port=htons(0);
addr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *) &addr,sizeof(addr))<0)
{
printf("bind socket failed\n");
return -2;
}
len=sizeof(raddr);
if(getsockname(sockfd,(struct sockaddr *)&raddr,&len)<0)
{
printf("getsockname failed.\n");
return -3;
}
printf("bound name=%s,port= %d,\n",ntohl(raddr.sin_addr.s_addr),ntohs(raddr.sin_port));
return 0;
}
7.getpeername
int getpeername(int socket,(socket sockaddr*)address,socklen_t *address_len)返回与socket连接的那个套接字的地址,成功返回0,错误返回-1.
8.send 和recv函数:可以用来读取发送带外数据
#include<sys/socket.h>
ssize_t send(int socket,const void *buffer,size_t length,int flags);
ssize_t recv(int socket,void *buffer,size_t length,int flags);
flags
send
MSG_OOB:带外数据,流套接字特有的,正常的数据流之外发送的
MSG_DONTROUTE :不在消息中包含路由信息
recv
MSG_OOB 带外数据
MSG_PEEK 窥视数据但不读取它们
MSG_WAITALL 请求函数阻塞直至所请求的数据全部收到
成功返回字节数,失败返回-1
9数据报套接字操作:将数据集中为一个包,为每个包单独指定目的地址,并且每个包独立进行通信,不允许有listen和accept函数
#include<sys/socket.h>
int recvfrom(int socket,void *buffer,size_t size,int flags,struct sockaddr *from,size_t *addrlen);
int sendto(int socket,void *buffer,size_t size,int flags,struct sockaddr *from,size_t *addrlen);