linux下SOCKET编程一

来源:互联网 发布:数据新闻能否成为主流 编辑:程序博客网 时间:2024/06/06 06:58

一、socket运行过程


        服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

二、socket实现过程

2.1 socket()函数

 int  socket(int protofamily, int type, int protocol);//返回sockfd

      socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

      正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

  • protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)AF_INET6(IPV6)AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。
  • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
注意:当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()listen()时系统会自动随机分配一个端口。


2.2 bind()函数

      正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INETAF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

   int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 
  • addrlen:对应的是地址的长度。
      通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

2.3 listen()、connect()函数

如果作为一个服务器,在调用socket()bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

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

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

2.4 accept()函数

          TCP服务器端依次调用socket()bind()listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd

参数sockfd
参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数addr
这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len
如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

         如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。

注意

      accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。

2.5、read()、write()等函数

万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

2.6、close()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

#include <unistd.h>int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

三、SOCKET 之TCP通信例程
1、客户端
#include<stdio.h>  #include<stdlib.h>  #include<string.h>  #include<errno.h>  #include<sys/types.h>  #include<sys/socket.h>  #include<netinet/in.h>  #define MAXLINE 4096     int main(int argc, char** argv)  {      int    sockfd, n,rec_len;      char    recvline[4096], sendline[4096];      char    buf[MAXLINE];      struct sockaddr_in    servaddr;        if( argc != 2){      printf("usage: ./client <ipaddress>\n");      exit(0);      }        if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){      printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);      exit(0);      }        memset(&servaddr, 0, sizeof(servaddr));      servaddr.sin_family = AF_INET;      servaddr.sin_port = htons(8000);      if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){      printf("inet_pton error for %s\n",argv[1]);      exit(0);      }        if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){      printf("connect error: %s(errno: %d)\n",strerror(errno),errno);      exit(0);      }        printf("send msg to server: \n");      fgets(sendline, 4096, stdin);      if( send(sockfd, sendline, strlen(sendline), 0) < 0)      {      printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);      exit(0);      }      if((rec_len = recv(sockfd, buf, MAXLINE,0)) == -1) {         perror("recv error");         exit(1);      }      buf[rec_len]  = '\0';      printf("Received : %s ",buf);      close(sockfd);      exit(0);  }  

2、服务端
#include<stdio.h>  #include<stdlib.h>  #include<string.h>  #include<errno.h>  #include<sys/types.h>  #include<sys/socket.h>  #include<netinet/in.h>  #define DEFAULT_PORT 8000  #define MAXLINE 4096  int main(int argc, char** argv)  {      int    socket_fd, connect_fd;      struct sockaddr_in     servaddr;      char    buff[4096];      int     n;      //初始化Socket      if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){      printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);      exit(0);      }      //初始化      memset(&servaddr, 0, sizeof(servaddr));      servaddr.sin_family = AF_INET;      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。      servaddr.sin_port = htons(DEFAULT_PORT);//设置的端口为DEFAULT_PORT        //将本地地址绑定到所创建的套接字上      if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){      printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);      exit(0);      }      //开始监听是否有客户端连接      if( listen(socket_fd, 10) == -1){      printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);      exit(0);      }      printf("======waiting for client's request======\n");      while(1){  //阻塞直到有客户端连接,不然多浪费CPU资源。          if( (connect_fd = accept(socket_fd, (struct sockaddr*)NULL, NULL)) == -1){          printf("accept socket error: %s(errno: %d)",strerror(errno),errno);          continue;      }  //接受客户端传过来的数据      n = recv(connect_fd, buff, MAXLINE, 0);  //向客户端发送回应数据      if(!fork()){ /*紫禁城*/          if(send(connect_fd, "Hello,you are connected!\n", 26,0) == -1)          perror("send error");          close(connect_fd);          exit(0);      }      buff[n] = '\0';      printf("recv msg from client: %s\n", buff);      close(connect_fd);      }      close(socket_fd);  }  

四、测试程序流程

编译server.c

gcc -o server server.c

启动进程:

./server

显示结果:

======waiting for client's request======

并等待客户端连接。

编译 client.c

arm-none-linux-gnueabi-gcc -o client client.c -static

客户端去连接server:

./client 192.168.1.101



0 0
原创粉丝点击