分享

POSIX Socket编程

 sky_feiyang 2015-05-05
一、套接字数据结构

1、通用套接字地址
struct   sockaddr 
{
sa_family_t   sa_family;   //通信类型,对于IPV4为AF_INET
char   sa_data[14];   //用来保存IP地址和端口信息,一般不用
}

2、IPV4套接字地址
struct   sockaddr_in
{
unsigned   short   sin_len; //IPV4地址长度
sa_family_t sin_family; //通信类型
unsigned    short   int   sin_port; //端口号
struct   in_addr   sin_addr; //IP地址
unsigned   char   sin_zero[8]; //补充字段
}
其中struct   in_addr为:
struct   in_addr
{
uint32_t   s_addr;//32位IP地址,网络字节顺序

}

3、hostent
struct   hostent
{
char*    h_name;//主机的正式名
char**   h_aliases;//主机的别名
int   h_addrtype;//主机的地址类型,IPV4为AF_INET
int   h_length;//地址长度,IPV4为32
char**   h_addr_list;//主机的IP地址列表
}
#define   h_addr   h_addr_list[0]//主机的第一个IP地址

POSIX中的数据类型
数据类型 说明 头文件
int8_t 带符号的8位整数 <sys/types.h>
uint8_t 无符号的8位整数 <sys/types.h>
int16_t 带符号的16位整数 <sys/types.h>
uint16_t 无符号的16位整数 <sys/types.h>
int32_t 带符号的32位整数 <sys/types.h>
uint32_t 无符号的32位整数 <sys/types.h>
sa_family_t 套接字地址结构的地址族 <sys/socket.h>
socklen_t 套接字地址结构的长度,一般为uint32_t <sys/socket.h>
int_port_t TCP或者UDP端口号,一般为uint16_t <netinet/in.h>
in_addr_t IPV4地址,一般为uint32_t <netinet/in.h>

二、基础函数

1、主机字节序和网络字节序
#include<netinet/in.h>
uint32_t  htonl(uint32_t   hostlong)
uint16_t   htons(uint16_t   hostsho以上rt)
以上两个函数返回网络字节序
     
        uint32_t   ntohl(uint32_t   netlong)
uint16_t   ntohs(uint16_t   netshort)
以上两个函数返回主机字节序

2、字节操作函数
#include<string.h>
void   memset(void*   dest,int   c,size_t   len)
void   memcpy(void*   dest,const   void*   src,size_t   nbytes)
int   memcmp(const   void*   ptr1,const   void*   ptr2,size_t   nbytes)

3、整数与IP地址转换
TCP/IP中的IP地址是以''.''隔开的十进制的数,而套接字中用的是32位的网络字节序的二进制数值。

#include<arpa/inet.h>
int   inet_aton(const   char*   straddr,struct   in_addr*   addrptr)
若成功则返回1,否则返回0
参数:straddr为点分十进制字符串,结果保存在addrptr所指的内存中


char*   inet_ntoa(struct   in_addr   inaddr)
若成功则返回点分十进制数串的指针,否则返回NULL
参数:inaddr为32位网络字节的整数,返回点分十进制数串的指针

in_addr_t   inet_addr(const   char*   straddr)
若成功则返回32位的二进制网络字节序的地址,否则返回INADDR_NONE(表示一个不存在的IP地址,其实就是255.255.255.255,就是-1)
参数:straddr为点分十进制字符串,返回为32位的二进制网络字节序的地址

4、域名与IP地址的转换
#include<netdb.h>
struct   hostent*   gethostbyname(const   char*   hostname)
struct   hostent*   gethostbyaddr(const   char*   addr,size_t   len,int   family)
若成功返回hostent结构指针,否则返回空指针,同时设置h_errno,可以调用hstrerror()函数获取h_errno的值

h_errno的取值:
HOST_NOT_FOUND 找不到主机
TRY_AGAIN 出错重试
NO_RECOVERY 不可修复性错误
NO_DATA 指定的名字有效,但是没有记录

5、其他常用函数:
若IP地址设为INADDR_ANY则表示本机IP,这时可以在浏览器中输入
http://localhost:端口号/
作为客户端向服务器发出链接请求

一般出错信息可以用perror来输出
void   perror(const   char*   s)
该函数除了会输出字符串s,还会输出错误原因

三、TCP编程

头文件:#include<sys/socket.h>
服务器流程:socket()→bind()→listen()→accept()→recv()<>send()→close()
客户端流程:socket()→connect()→send()<>recv()→close()

1、创建套接字

int   socket(int   family,int   type,int   ptotocol)
若成功则返回套接字描述符,否则返回-1
参数:family取值:PF_INET(等价于AF_INET)、PF_INET6(等价于AF_INET6)
    (PF代表Protocl Family协议族,AF代表Address Family地址族)
    type取值:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW
    ptotocol一般取0

2、绑定端口

int   bind(int   sockfd,const   struct   sockaddr*   my_addr,socklen_t   addrlen)
若成功返回0,否则返回-1
若出错,则可以在errno中捕获:
EBADF:参数sockfd不合法
EACCEDD:权限不足
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket

3、等待监听

int   listen(int   sockfd,int   backlog)
若成功则返回0,否则返回-1
参数:sockfd表示要监听的套接字,backlog表示最多同时处理的请求数(若为IPV4则最多为128),若超过这个个数,则客户端会受到ECONNREFUSED错误
若出错,则可以在errno中捕获
EBADF:参数sockfd不合法
EACCEDD:权限不足
EOPNOTSUPP:指定的socket不支持listen

4、接受链接

int   accept(int   sockfd,struct   sockaddr*   addr,socketlen_t*   addrlen)
若成功则返回新的套接字,否则返回-1
参数:sockfd表示处于监听的套接字,addr表示客户端的信息
accept接受一个链接时会返回一个新的套接字,以后的数据传输就用这个新的套接字处理 若出错,则可以用errno捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EOPNOTSUPP:制定的socket不是SOCK_STREAM
EPERM:防火墙拒绝这个链接
ENOBUFS:系统缓冲内存不足
ENOMEM:核心内存不足

5、请求链接

int   connect(int   sockfd,const   struct   sockaddr*   serv_addr,int   addrlen)
若成功返回0,否则返回-1
参数:sockfd表示已经建立好的套接字,serv_addr保存服务器信息
若出错,可以在errno中捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EISCONN:参数sockfd的套接字已经处于链接状态
ECONNREFUSED:链接请求被拒绝
ETIMEDOUT:超时
ENETUNREACH:无法传送数据包到指定主机
EAFNOSUPPORT:sockaddr结构的sa_family不正确
EALREADY:socket不能阻断,但是以前的链接操作还未完成

6、数据发送和接收

int   send(int   sockfd,const   void*   buf,int   len,unsigned   int   flags)
int   recv(int   sockfd,void*   buf,int   len,unsigned   int   flags)
若成功则返回已经发送或者接收的字节数,否则返回-1
flags一般置0
若出错,可以在errno中捕获:
EBADF:参数sockfd不合法
EFAULT:参数addr指针指向无法存取的空间
ENOTSOCK:参数sockfd是一个文件描述符,而不是socket
EINTR:进程被信号中断
EAGAIN:此动作会中断进程,但参数sockfd的套接字不可中断
ENOBUFS:系统的缓冲内存不足
ENOMEM:核心内存不足
EINVAL:函数参数不正确

7、write和read函数

#include<unistd.h>
ssize_t   write(int   fd,const   void*   buf,size_t   count)
ssize_t   read(int   fd,void*   buf,size_t   count)
若成功返回写入和读取的字节数,否则返回-1

注意:write等价于send,read等价于recv

8、关闭套接字

#include<unistd.h>
int   close(int   fd)
若成功返回0,否则返回-1

服务器:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<netdb.h>
#include<errno.h>
#include<unistd.h>
#include<arpa/inet.h>

#define BUFSIZE 1024//定义缓冲区的大小

int main(int argc,char* argv[])
{
    int sockfd,newsockfd;
    sockaddr_in server_addr;//服务器的套接字地址
    sockaddr_in client_addr;//客户端的套接字地址
    int port;
    char buf[BUFSIZE];//发送缓冲区
    socklen_t addr_len=sizeof(sockaddr_in);
    if(argc!=2)//判断命令行参数
    {
        printf("Arguments error! Usage:%s port\n",argv[0]);
        exit(1);
    }

    //获取端口号
    if((port=atoi(argv[1]))<0)
    {
        printf("Port error! Usage:%s port\n",argv[0]);
        exit(1);
    }

    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)//创建服务器Socket
        {
            printf("Create socket error:%s\n",strerror(errno));
            exit(1);
        }
    printf("Create server socket success!Socket id:%d\n",sockfd);

    //初始化服务器套接字地址
    memset(&server_addr,0,sizeof(sockaddr_in));//清零
    server_addr.sin_family=AF_INET;
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    server_addr.sin_port=htons(port);

    //绑定
    if(bind(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1)
        {
            printf("Bind error:%s\n",strerror(errno));
            exit(1);
        }
    printf("Bind port success!local port:%d\n",port);

    //监听
    if(listen(sockfd,5)==-1)
        {
            printf("Listen error:%s\n",strerror(errno));
            exit(1);
        }
    printf("Listening...\n");

    //服务器阻塞,等待接收连接请求,知道客户端程序发送连接请求
    while(1)
        {
            //接收请求
            if((newsockfd=accept(sockfd,(sockaddr*)(&client_addr),&addr_len))==-1)
                {
                    printf("Accept error:%s\n",strerror(errno));
                    exit(1);
                }
            //打印客户端IP
            printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
            //提示用户输入要发送的数据
            printf("Connected successful,please input the mesage[<1024 bytes]:\n");

            //数据存放在buf缓冲区,若正确则返回地址等于缓冲区地址
            if(fgets(buf,sizeof(buf),stdin)!=buf)
                {
                    printf("fgets error!\n");
                    exit(1);
                }

            //调用write发送数据,等价于send(newsockfd,buf,strlen(buf),0)
            //用strlen不用sizeof的原因是只发送用户输入的字符串
            if(write(newsockfd,buf,strlen(buf))==-1)
                {
                    printf("Write error:%s\n",strerror(errno));
                    exit(1);
                }

            close(sockfd);

            return 0;
        }
}


客户端:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<netdb.h>
#include<errno.h>
#include<unistd.h>

#define BUFSIZE 1024

int main(int argc,char* argv[])
{
    int sockfd;
    char buffer[BUFSIZE];
    sockaddr_in server_addr;
    hostent* hostname;
    int port,nbytes;

    if(argc!=3)
        {
            printf("Arguments error! Usage:%s hostname prot\n",argv[0]);
            exit(1);
        }

    //获得主机名
    if((hostname=gethostbyname(argv[1]))==NULL)
        {
            printf("Get hostname error\n");
            exit(1);
        }

    //获得端口号
    if((port=atoi(argv[2]))<0)
        {
            printf("Usage:%s hostname port\n",argv[0]);
            exit(1);
        }

    //建立socket
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
            printf("Create socket error:%s\n",strerror(errno));
            exit(1);
        }
    printf("Create client socket success! Socket id:%d\n",sockfd);

    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(port);
    server_addr.sin_addr=*((in_addr*)hostname->h_addr);

    //发起连接请求
    if(connect(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1)
        {
            printf("Connect error:%s\n",strerror(errno));
            exit(1);
        }

    //连接成功
    printf("Connect success!\n");
    if((nbytes=read(sockfd,buffer,BUFSIZE))==-1)
        {
            printf("Read error:%s\n",strerror(errno));
            exit(1);
        }
    buffer[nbytes]='\0';
    printf("I have received:%s\n",buffer);

    close(sockfd);

    return 0;
}

执行:    ./server   端口号
./client   localhost   端口号





































































    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多