Socket 编程实例

       首先介绍两个用于处理网络通信中地址问题的结构体:sockaddr和sockaddr_in

sockaddr

struct sockaddr {

    unsigned short sa_family; /* address family, AF_xxx */

    char sa_data[14]; /* 14 bytes of protocol address */

};

sa_family:是一个address family,也就是地址族;

sa_data:长度为14个字节,包含套接字(socket)中的目标地址和端口信息 。

sockaddr定义的头文件:sys/socket.h

sockaddr的缺陷是:把目标地址和端口信息混在同一个数组(sa_data)里

sockaddr_in

struct sockaddr_in {

    short int sin_family; /* Address family */

    unsigned short int sin_port; /* Port number */

    struct in_addr sin_addr; /* Internet address */

    unsigned char sin_zero[8]; /* Same size as struct sockaddr */

};

       sockaddr_in解决了sockaddr 的缺陷,实际上相当于把sockaddr 中的sa_data分成了3个部分:

  • sin_port 端口号
  • sin_addr IP地址
  • sin_zero 补齐空格

       sockaddr_in 定义的头文件:netinet/in.h和arpa/inet.h

小结

       二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

       在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。也就是说,平时我们用的还是sockaddr_in,只是有些函数要求传入的参数是sockaddr,所以需要在传入时进行强制类型转换。

       概括一下:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。


实例介绍

       本实例分为客户端(Client)和服务端(Server)两个部分。

       服务端首先建立起socket,并与本地端口进行绑定,然后开始接收从客户端的连接请求并与该客户端的连接,接下来就可以接收客户端发送的消息了。

       客户端在建立socket之后调用connect函数建立连接。

服务端的源代码如下:

//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <studio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT 4321 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5


int main()
{
    struct socketaddr_in server_sockaddr,client_sockaddr;

    int sin_size,recvbytes;

    int sockfd,client_fd;

    char buf[BUFFER_SIZE];

    if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM0)))
    {
        perror("socket");

        exit(1);
    }

    printf("Socket id = %d\n",sockfd);

    server_sockaddr.sin_family = AF_INET;

    server_sockaddr.sin_port = htons(PORT);

    server_sockaddr.sin_addr.s_addr = INADDR_ANY;

    memset(server_sockaddr.sin_zero,0,8);

    int i = 1;

    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i));

    //绑定函数bind
    if( -1 == bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) )
    {
        perror("bind");

        exit(1);
    }

    printf("Bind Success\n");

    //listen函数
    if( -1 == listen(sockfd,MAX_QUE_CONN_NM) )
    {
        perror("listen");

        exit(1);
    }

    printf("listening...\n");

    //accept函数
    if( -1 == accept(sockfd,(struct sockaddr *)&server_sockaddr,&sin_size))
    {
        perror("accept");

        exit(1);
    }

    //recv函数
    memset(buf,0,sizeof(buf));

    if( -1 == recv(client_fd,buf,BUFFER_SIZE,0))
    {
        perror(recv);

        exit(1);
    }

    printf("Received a message: %s\n",buf);

    close(fd);

    exit(0);

}

客户端的源代码如下:

//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <studio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT 4321 //端口号
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5

int main(int argc, char *argv[])
{
    int sockfd,sendbytes;

    char buf[BUFFER_SIZE];

    struct hostent *host;

    struct sockaddr_in server_addr;

    if(arg < 3)
    {
        fprintf(stderr,"USAGE: ./Client Hostname(or ip address) Text\n");

        exit(1);
    }

    //地址解析函数
    if( NULL == (host = gethostbyname(argv[1])))
    {
        perror("gethostbyname");

        exit(1);
    }

    memset(buf,0,sizeof(buf));

    sprintf(buf,"%s",argv[2]);

    //创建socket
    if( -1 == (sockfd = socket(AF_INET,SOCK_STREAM,0)) )
    {
        perror("socket");

        exit(1);
    }

    server_sockaddr.sin_family = AF_INET;

    server_sockaddr.sin_port = htons(PORT);

    server_addr.sin_addr = *( (struct in_addr*)host->h_addr );

    memset(server_addr.sin_zero,0,8);

    if( -1 == (connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr))) )
    {
        perror("connect");

        exit(1);
    }

    //发送消息给服务器端
    if( -1 == (sendbytes = send(sockfd,buf,strlen(buf),0)) )
    {
        perror("send");

        exit(1);
    }

    close(sockfd);

    exit(0);

}

使用方法

       运行时需要先启动服务器,再启动客户端。
       在Ubuntu中可以这样使用:

  • 打开一个shell,并执行命令

./server

  • 保持上一个shell打开,并打开第二个shell,执行命令

./client localhost Hello


注意事项

       在client.c文件中不要忘记添加netdb.h头文件

netdb.h头文件里定义了与网络有关的结构、变量类型、宏、函数等。

/*通过IP地址获得主机有关的网络信息*/
struct hostent*  gethostbyaddr(const void *addr, size_t len, int type);

/*通过主机名获得主机的网络信息*/
struct hostent*  gethostbyname(const char *name);

       因为我们在client.c中用到了gethostbyname函数,所以需要添加这个头文件。