Linux TCP/IP Socket编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/specialshoot/article/details/50716691

一.Socket TCP/IP流程图

socket流程图socket流程图

socket流程图

二.关键数据结构/结构体

sockaddr_in

在列出sockaddr_in结构体之前先将sockaddr结构体说明,此数据结构用作bind、connect、recvfrom、sendto等函数的参数,指明地址信息。sockaddr定义如下:

struct sockaddr {
    unsigned short sa_family; 
    char sa_data[14]; 
};

但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in。

sockaddr_in结构体在<netinet/in.h>中定义,定义如下:

struct sockaddr_in{
    short int sin_family;//协议族,在socket编程中只能是AF_INET
    unsigned short int sin_port;//存储端口号(使用网络字节顺序)
    struct in_addr sin_addr;//存储IP地址,使用in_addr这是数据结构
    unsigned char sin_zero[8];//为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
};

struct in_addr{//实际上就是32位ip地址
    unsigned long s_addr;//按照网络字节顺序存储IP地址
};

typedef struct in_addr{
    union{
        struct{unsigned char s_b1,s_b2,s_b3,s_b4;} S_un_b;
        struct{unsigned short s_w1,s_w2;} S_un_w;
        unsigned long S_addr;
    } S_un;
};

三.socket关键函数

1.socket()

socket()函数原型:

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
参数说明:

  • domain:协议族,对于socket要使用AF_INET
  • type:套接字参数类型,设置为SOCK_STREAM或SOCK_DGRAM。一般使用SOCK_STREAM,因为它是基于TCP的,能保证数据正确传送到对方。SOCK_DGRAM是基于UDP的,无法保证数据正确传送到对方。如果大家使用UDP方式传输,要使用SOCK_DGRAM
  • protocol:制定协议。常用协议有IPPROTO_TCP,IPPTOTO_UDP,IPPROTO_SCTP,IPPROTO_TIPC等,它们分别对应TCP,UDP,STCP,TIPC协议。TCP协议传入参数0即可
  • 返回值:返回一个套接字描述符,出错返回-1

2.bind()

bind()函数原型:

int bind(int sock_fd,struct sockaddr_in *my_addr, int addrlen);
参数说明:

  • sock_fd:套接字,传入的参数是socket()函数的返回值
  • my_addr:一个指向包含有本机IP地址及端口号等信息得到sockaddr类的指针
  • addrlen:地址长度,传入sizeof(my_addr)即可
  • 返回值:成功返回0,失败返回-1;

3.connect()

connect()函数原型:

 int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
参数说明:

  • sock_fd:套接字,传入的参数是socket()函数的返回值
  • serv_addr:包含远端主机IP地址和端口号的指针
  • addrlen:同上,传入sizeof(serv_addr)即可
  • 返回值:成功返回0,失败返回-1

4.listen()

listen()函数原型:

int listen(int sock_fd, int backlog);
参数说明:

  • sock_fd:同上,传入的参数时socket()函数的返回值
  • backlog:指定在请求队列中允许的最大请求数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20
  • 返回值:成功返回0,失败返回-1

5.accept()

accept()函数原型:

#include<sys/socket.h>
int accept(int sock_fd,(struct sockaddr*) addr,int* addrlen);
参数说明:

  • sock_fd:同上,传入socket的返回值
  • addr:指向sockaddr_in变量的指针
  • addrlen:地址长度,传入&sizeof(addr)即可。注意:这个accept()函数传入的是&sizeof(addr)而不是sizeof(addr)!!!
  • 返回值:成功返回新的套接字描述符,错误返回-1

6.write()

write()函数原型:

#include<unistd,h>
ssize_t write(int fd,const void *buf,size_t nbytes)
参数说明:

  • fd:文件描述符,这里传入socket返回值
  • buf:数据缓冲区
  • nbytes:最大输出字节计数
  • 返回值:返回实际写入的字节数,发生错误返回-1

7.read()

read()函数原型:

#include <unistd.h>    
ssize_t read(int fd, void *buf, size_t count);  
参数说明:

  • fd:文件描述符,这里传入socket返回值
  • buf:数据缓冲区
  • count:请求读取的字节数
  • 返回值:返回实际读取到的字节数,返回0表示已到达文件尾或无数据可读,错误返回-1

8.send()

send()函数原型:

int send(int sockfd,const void* buf,int len,int flags);
参数说明:

  • sockfd:套接字,传入socket()返回值
  • buf:指向发送的数据的指针
  • len:数据的字节长度
  • flags:特殊传输标识,其值多为0。具体值可以参看博客http://183132459shp.blog.163.com/blog/static/43151635201212412117565/
  • 返回值:返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你必须发送剩下的数据。出错返回-1

9.recv()

recv()函数原型:

int recv(int sockfd,void *buf,int len,int flags) 
参数说明:

10.sendto()

sendto()函数原型:

int sendto(int sockfd,const void* buf,int len,unsigned int flags,const struct sockaddr* to,int tolen);
参数说明:

  • sockfd:套接字,传入socket()的返回值
  • buf:指向发送的数据的指针
  • len:数据的字节长度
  • flags:特殊传输标识,其值多为0
  • to:指向包含目的IP地址和端口号的数据结构sockaddr的指针
  • tolen:sizeof(struct sockaddr)
  • 返回值:返回实际发送的字节数,如果出错则返回-1

11.recvfrom()

recvfrom()函数原型:

int recvfrom(int sockfd,void* buf,int len,unsigned int flags struct sockaddr* from,int* fromlen);
recvfrom参数说明:

  • sockfd:套接字,传入socket()的返回值
  • buf:指向接收数据的指针
  • len:数据的字节长度
  • flags:特殊传输标识,其值多为0
  • from:指向本地计算机中包含源IP地址和端口号的数据结构sockaddr的指针
  • tolen:sizeof(struct sockaddr)
  • 返回值:返回接收到的字节数,如果出错则返回-1

12.close()

close()函数原型:

int close(sock_fd);
参数说明:

  • sock_fd:套接字
  • 返回值:成功返回0,失败返回-1

13.shutdown()

shutdown()可以拥有比close()更多的控制权。它允许你在某一个方向切断通信,或者切断双方的通信。shutdown()函数原型:

int shutdown(int sockfd,int how);
  • sockfd:套接字
  • how:你希望切断通信的标志位,可选参数如下:
    • 0:Further receives are disallowed
    • 1:Further sends are disallowed
    • 2:Further sends and receivers are disallowed
  • 成功返回0,失败返回-1

四.write-read,send-recv,sendto-recvfrom的选择

对于TCP/IP可以使用write-read和send-recv。使用send-recv功能上更优胜一些。具体可见博客http://blog.csdn.net/deng529828/article/details/6245254所述。本例中使用send-recv两个函数代替write-read。

五.代码!!!(源自http://www.oschina.net/code/snippet_97047_675

服务器端:

//http://www.oschina.net/code/snippet_97047_675
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero
#define HELLO_WORLD_SERVER_PORT    6666 
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
 
int main(int argc, char **argv)
{
    //设置一个socket地址结构server_addr,代表服务器internet地址, 端口
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
 
    //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
    int server_socket = socket(PF_INET,SOCK_STREAM,0);
    if( server_socket < 0)
    {
        printf("Create Socket Failed!");
        exit(1);
    }
    int opt =1;
    setsockopt(server_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
     
    //把socket和socket地址结构联系起来
    if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    {
        printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT); 
        exit(1);
    }
 
    //server_socket用于监听
    if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
    {
        printf("Server Listen Failed!"); 
        exit(1);
    }
    while (1) //服务器端要一直运行
    {
        //定义客户端的socket地址结构client_addr
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);
 
        //接受一个到server_socket代表的socket的一个连接
        //如果没有连接请求,就等待到有连接请求--这是accept函数的特性
        //accept函数返回一个新的socket,这个socket(new_server_socket)用于同连接到的客户的通信
        //new_server_socket代表了服务器和客户端之间的一个通信通道
        //accept函数把连接到的客户端信息填写到客户端的socket地址结构client_addr中
        int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
        if ( new_server_socket < 0)
        {
            printf("Server Accept Failed!\n");
            break;
        }
         
        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
        if (length < 0)
        {
            printf("Server Recieve Data Failed!\n");
            break;
        }
        char file_name[FILE_NAME_MAX_SIZE+1];
        bzero(file_name, FILE_NAME_MAX_SIZE+1);
        strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
        printf("%s\n",file_name);
        FILE * fp = fopen(file_name,"r");
        if(NULL == fp )
        {
            printf("File:\t%s Not Found\n", file_name);
        }
        else
        {
            bzero(buffer, BUFFER_SIZE);
            int file_block_length = 0;
            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
            {
                printf("file_block_length = %d\n",file_block_length);
                //发送buffer中的字符串到new_server_socket,实际是给客户端
                if(send(new_server_socket,buffer,file_block_length,0)<0)
                {
                    printf("Send File:\t%s Failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
            fclose(fp);
            printf("File:\t%s Transfer Finished\n",file_name);
        }
        //关闭与客户端的连接
        close(new_server_socket);
    }
    //关闭监听用的socket
    close(server_socket);
    return 0;
}
说明:

htons将主机的无符号短整形转换成网络字节顺序。

INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。

setsockopt函数获取或者设置与某个套接字关联的选项,函数原型:

#include <sys/types.h>
#include <sys/socket.h>

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
参数说明:

  • sock:套接字
  • level:选项所在的协议层,可选参数如下:
    • SOL_SOCKET: 基本套接口
    • IPPROTO_IP: IPv4套接口
    • IPPROTO_IPV6: IPv6套接口
    • IPPROTO_TCP: TCP套接口
  • optname:需要访问的选项名,对于level的选项,有不同的参数,具体配置可见http://blog.csdn.net/l_yangliu/article/details/7086256本例中选择的时SQL_SOCKET中的SO_REUSERADDR选项代表允许重用本地地址和端口。
  • optval:指向包含新选项值的缓冲
  • optlen:选项的长度
  • 返回值:成功返回0,失败返回-1
客户端:

//http://www.oschina.net/code/snippet_97047_675
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero
#define HELLO_WORLD_SERVER_PORT    6666 
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
 
int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: ./%s ServerIPAddress\n",argv[0]);
        exit(1);
    }
 
    //设置一个socket地址结构client_addr,代表客户机internet地址, 端口
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr)); //把一段内存区的内容全部设置为0
    client_addr.sin_family = AF_INET;    //internet协议族
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自动获取本机地址
    client_addr.sin_port = htons(0);    //0表示让系统自动分配一个空闲端口
    //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket
    int client_socket = socket(AF_INET,SOCK_STREAM,0);
    if( client_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }
    //把客户机的socket和客户机的socket地址结构联系起来
    if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
    {
        printf("Client Bind Port Failed!\n"); 
        exit(1);
    }
 
    //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //服务器的IP地址来自程序的参数
    {
        printf("Server IP Address Error!\n");
        exit(1);
    }
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t server_addr_length = sizeof(server_addr);
    //向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接
    if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
    {
        printf("Can Not Connect To %s!\n",argv[1]);
        exit(1);
    }
 
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Server:\t");
    scanf("%s", file_name);
     
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
    //向服务器发送buffer中的数据
    send(client_socket,buffer,BUFFER_SIZE,0);

    FILE * fp = fopen(file_name,"w");
    if(NULL == fp )
    {
        printf("File:\t%s Can Not Open To Write\n", file_name);
        exit(1);
    }
     
    //从服务器接收数据到buffer中
    bzero(buffer,BUFFER_SIZE);
    int length = 0;
    while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
    {
        if(length < 0)
        {
            printf("Recieve Data From Server %s Failed!\n", argv[1]);
            break;
        }
        int write_length = fwrite(buffer,sizeof(char),length,fp);
        if (write_length<length)
        {
            printf("File:\t%s Write Failed\n", file_name);
            break;
        }
        bzero(buffer,BUFFER_SIZE);    
    }
    printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
     
    close(fp);
    //关闭socket
    close(client_socket);
    return 0;
}
说明:

inet_aton()用来将参数cp所指的网络地址字符串转换成网络使用的二进制的数字。函数原型如下:

int inet_aton(const char *cp, struct in_addr*addr);
参数说明:

  • cp:包含ASCII表示的IP地址
  • addr:存储新的ip的in_addr类型的地址。至于in_addr,本博客开头已经介绍。
至此,客户端和服务器端代码完成。运行效果如下:

运行效果

猜你喜欢

转载自blog.csdn.net/specialshoot/article/details/50716691