网络socket编程:客户端

下面是网络socket通信的基本流程:
在这里插入图片描述
socket客户端代码:

#include <stdio.h>          
#include <sys/types.h>          
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <getopt.h>


#define  MSG_NUM  1024                               
#define  MSG_STR  "This is a network socket program"

void print_usage(char *progname)
{
        printf("%s usage: \n", progname);
        printf("-i(--SERVER_IP): sepcify server IP address\n");
        printf("-p(--SERVER_PORT): sepcify server port.\n");
        printf("-h(--Help): print this help information.\n");
        return ;
}



int main(int argc,char **argv)
{
      int    sockfd = -1;
      int    rv  =-1;
      char   *SERVER_IP = NULL;
      int    SERVER_PORT = 0;
      int    rw;
      char   buf[MSG_NUM];
      struct sockaddr_in     servaddr;
      struct option longopts[] = {
      {"help", no_argument, NULL, 'h'},
      {"SERVER  IP", required_argument, NULL, 'P'},
      {"SERVER  PORT", required_argument, NULL, 'P'},
      {0, 0, 0, 0}
      };
      while( (rw=getopt_long(argc, argv, "i:p:h", longopts, NULL)) != -1 ) 
            {
                switch(rw)
                {
                    case 'i':
                        SERVER_IP=optarg;
                        break;
                    case 'p':
                        SERVER_PORT=atoi(optarg);
                        break;
                    case 'h':
                        print_usage(argv[0]);
                        return 0;
                }

            }

        if( !SERVER_IP || !SERVER_PORT )
            {
                print_usage(argv[0]);
                return 0;
                
            }
                  
  
      sockfd=socket(AF_INET, SOCK_STREAM,0);
      if(sockfd<0)
      {
         printf("Create socket failure:%s\n",strerror(errno));
         return 0;
      }//创建一个socket描述字,它唯一标识一个socket。存放在sockfd中,后续操作都有用到。
      printf("Create socket[%d] successfully!\n",sockfd);
      
      memset(&servaddr, 0, sizeof(servaddr));  ;
      servaddr.sin_family=AF_INET;             
      servaddr.sin_port = htons(SERVER_PORT);    
      inet_aton(SERVER_IP, &servaddr.sin_addr);

      rv=connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
      if(rv<0)
      {
         printf("To socket[%s:%d] failure:%s\n ",SERVER_IP,SERVER_PORT,strerror(errno));
         return -1;
         goto cleanup; 
      }
      printf("To connect socket[%d] successfully!",sockfd);



      rv=write(sockfd, MSG_STR, sizeof(MSG_STR));
      if(rv<0)
      {
          printf("Write data to socket[sockfd] failure:%s\n",strerror(errno));
          return -2;
         goto cleanup; 
      }
      printf("Write data to socket[%d] successfully!\n",sockfd);
     while(1)
     {
         memset(buf,0,sizeof(buf));
         rv=read(sockfd,buf,sizeof(buf));
      if(rv<0)
      {
          printf("Read data from socket[%d] failure:%s\n",sockfd,strerror(errno));
          return -3;
         goto cleanup; 

      }
       else if (rv==0)
      {
          printf("The socket[%d] has disconnected!",sockfd);
          return -4;
         goto cleanup; 
      }
    
      printf("Read %d bytes data from socket[%d]:%s\n",rv,sockfd,buf);
     }
cleanup: 
close(sockfd);
     return 0; 

}

socket操作API函数

socket就提供了各种操作对应的函数接口,下面介绍几个上面代码中用到的参数。
————————————————————————————————————————————

socket()函数

int socket(int domain, int type, int protocol);

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
domain:也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等)。
protocol:protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。当protocol为0时,会自动选择type类型对应的默认协议。

————————————————————————————————————————————

connect()函数

TCP客户端程序调用socket()创建socket fd之后,就可以调用connect()函数来连接服务器。如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求并使accept()返回,accept()返回的新的文件描述符就是对应到该客户的TCP连接,通过这两个文件描述符(客户端connect的fd和服务器端accept返回的fd)就可以实现客户端和服务器端的相互通信。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

◆ sockfd: 客户端的socket()创建的描述字
◆ addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息
◆ addrlen: socket地址的长度

在调用connect之前,我们需要先设置服务器端的IP地址和端口等信息到addr中去。

memset(&servaddr, 0, sizeof(servaddr));  ;
      servaddr.sin_family=AF_INET;             
      servaddr.sin_port = htons(SERVER_PORT);    
      inet_aton(SERVER_IP, &servaddr.sin_addr);
      rv=connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
      if(rv<0)
      {
         printf("To socket[%s:%d] failure:%s\n ",SERVER_IP,SERVER_PORT,strerror(errno));
         return -1;
         goto cleanup; 
      }
      printf("To connect socket[%d] successfully!",sockfd);

————————————————————————————————————————————

read()、write()等函数

客户端在connect()连接服务器,并且服务器通过accept()建立起这个TCP socket链接之后,就可以调用网络I/O函数进行读写操作了,即实现了网络通信。具体用法可查百度或参考我上一篇博客:https://blog.csdn.net/weixin_46378291/article/details/104771835

————————————————————————————————————————————

close()、shutdown()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用close()来关闭一样。close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

int close(int fd);

如果对socket fd调用close()则会触发该TCP连接断开的四路握手,有些时候我们需要数据发送出去并到达对方之后才能关闭socket套接字,则可以调用shutdown()函数来半关闭套接字:

int shutdown(int sockfd, int how);

如果how的值为 SHUT_RD 则该套接字不可再读入数据了; 如果how的值为 SHUT_WR 则该套接字不可再发送数据了; 如果how的值为 SHUT_RDWR 则该套接字既不可以读,也不可以写数据了。

————————————————————————————————————————————

inet_pton()和inet_ntop()函数

在此前的代码中我们使用的inet_aton()或inet_ntoa()函数完成IPv4点分十进制字符串和32位整形数据之间的互相转换。

 inet_aton(SERVER_IP, &servaddr.sin_addr);

inet_aton() 转换网络主机地址ip(如192.168.1.10)为二进制数值,并存储在struct in_addr结构中,即第二个参数*inp,函数返回非0表示cp主机有地有效,返回0表示主机地址无效。但这两个函数只适合于IPv4的地址。下面这两个函数可以同时兼容IPv4和IPv6的地址:

int inet_pton(int family, const char *strptr, void *addrptr);
//将点分十进制的ip地址转化为用于网络传输的数值格式,返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//将数值格式转化为点分十进制的ip地址格式,返回值:若成功则为指向结构的指针,若出错则为NULL

————————————————————————————————————————————

网络字节序和主机字节序

主机字节序:就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。
所以,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。函数 htons()htolnl() 分别用来将 端口和IP地址转换成网络字节序。参考上面代码示例。

 servaddr.sin_port = htons(SERVER_PORT);
 serv_addr.sin_addr.s_addr = htonl(192.168.1.141);

这里通过调用两个函数 htons() 和 htolnl() 分别用来将 端口和IP地址转换成网络字节序,这两个函数名中的 h表示host, n表 示network, s表示short(2字节/16位), l表示long(4字节/32位)。因为端口号是16位的,所以我们用htons()把端口号从主机字节序转换成网络字节序, 而IP地址是32位的,所以我们用htonl()函数把IP地址从主机字节序转换成网络字节序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值