copyright:weishusheng          data:2015.5.26             

摘要:socket又叫套接字或者插口,它也是进程间通信的一种方式,实际上就是网络上的通信节点,应用程序只需要链接到socket就可以和网络上任何一个通信端点连接、传送数据。socket封装了通信的细节,我们可以不必关心通信协议内容而专注于应用程序开发。根据数据传送方式,socket分为面向连接的数据流通信和无连接的数据报通信。

1.创建socket

使用socket()函数创建socket对象,函数定义如下:

#include <sys/types.h>

#include <sys/socket.h>

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

socket()函数参数详解:

domain指定使用的域,通常取值为AF_INET和AF_INET6,AF_INET表示使用IPv4协议,AF_INET6表示使用IPv6协议。type指定数据传输方式,type取值SOCK_STREAM表示面向连接的数据流方式,type取值SOCK_DGRAM表示无连接的数据报方式,type取值SOCK_RAW表示原始模式。protocol一般取0。socket()成功返回创建的函数句柄值。

2.面向连接的socket通信实现

图1 面向连接的socket数据流通信

所有的面向连接socket数据流通信都遵循这个过程。

2.1服务器端工作流程:

(1)使用socket()创建socket

(2)使用bind()把创建的socket绑定到指定TCP端口

(3)调用listen()使socket处于监听状态

(4)客户端发送请求后,调用accept()接受客户端请求,建立连接

(5)与客户端发送或接收数据

(6)通信完毕,关闭socket

2.2客户端工作流程:

(1)使用socket()创建socket

(2)调用connect()向服务器端socket发起连接

(3)建立连接后,进行数据读写

(4)通信完毕,关闭socket

2.3通信过程使用了不同的函数,下面分别进行介绍

2.3.1 bind()函数

#include <sys/types.h>

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

sockfd是要绑定的socket句柄,my_addr指向一个sockaddr结构,里面保存IP地址和端口号,addrlen是sockaddr结构的大小。bind()如果绑定TCP端口成功,返回0,失败返回-1。

2.3.2 listen()函数

#include <sys/socket.h>

int listent(int s, int backlog);

s 是要监听的socket句柄,backlog指定最多可以监听的链接数量,默认是20个。如果listen调用成功,返回0,失败就返回-1.

2.3.3 accept()函数

#include <sys/types.h>

#include <sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t addrlen);

accept函数用于面向连接的套接字类型。accept()将从连接请求队列中获得连接信息,创建新的套接字,并返回该套接字的描述符。accept返回的是一个新套接字描述符,客户端可以通过这个描述符和服务器通信,而最初通过socket创建的套接字描述符依然用于监听客户端请求。

参数s是监听的套接字描述符,参数addr是sockaddr结构的指针,addrlen是结构的大小。如果accept()调用成功,返回创建的套接字句柄,失败返回-1,并设置全局变量为errno。

2.3.4 connect()函数

客户端创建套接字后就可以用connect连接服务器。

#include <sys/types.h>

#include <sys/socket.h>

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

connect()用于和服务器建立连接。sockfd是套接字句柄,serv_addr指向sockaddr结构,指定了服务器IP和端口;参数addlen是serv_addr结构大小。

2.3.5 客户端和服务器可以使用相同的发送和接受数据的函数,read()和write()就不再讲了;下面讲一下send()和recv()函数。

(1)发送函数send()

#include <sys/types.h>

#include <sys/socket.h>

ssize_t send(int s, const void *buf, size_t len, int flags);

参数s是套接字描述符,buf是要发送的数据缓冲,len是数据缓冲长度,flags一般置0;send如果发送成功返回发送的字节数,失败返回-1.

(2)接收函数recv()

#include <sys/types.h>

#include <sys/socket.h>

int recv(int s, void *buf, size_t len, int flags);

参数s指定要读取的套接字句柄,buf是存放数据的缓冲首地址,len指定接收缓冲大小,flags一般置0;recv读取到数据时返回读取到的字节数,失败返回-1.另外,如果对方关闭了套接字,recv返回0;

3.面向连接的套接字实例

下面将以一个echo实例来说明socket通信,其中echo_serv.c是服务器端代码,当收到客户端发送的字符就在屏幕上打印出来,并把字符串发送给客户端,如果客户端发送quit就结束。

(1)服务器端程序echo_serv.c

#include <string.h>

#define ECHO_PORT 8080
#define MAX_CLIENT_NUM 10

int main()
{
  int sock_fd;
  struct sockaddr_in serv_addr;
  int clientfd;
  struct sockaddr_in clientAdd;
  char buff[101];
  socklen_t len;
  int n;

  /* creat socket */
  sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  if( sock_fd == -1)
  {
    perror("creat socket error!");
    return 0;
  }
  else
  {

    printf("success to creat socket %d\n", sock_fd);
  }

  /*设置server地址结构*/
  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(ECHO_PORT);
  serv_addr.sin_addr.s_addr = htons(INADDR_ANY);     //表示监听所有客户端ip,如需指定监听指定的客户端ip,可在此处指定,也可指定一个范围
  bzero(&(serv_addr.sin_zero), 8);

  /*把地址和套接字绑定*/
  if(bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) != 0)
  {
    printf("bind address fail! %d\n",errno);
    close(sock_fd);
    return 0;
  }
  else
  {
    printf("success to bind address! \n");
  }

  /*设置套接字监听*/
  if(listen(sock_fd, MAX_CLIENT_NUM) != 0)
  {
    perror("listen socket error!");
    close(sock_fd);
    return 0;
  }
  else
  {
    printf("success to listen!\n");
  }

  /*创建新链接对应的套接字*/
  len = sizeof(clientAdd);
  clientfd = accept(sock_fd, (struct sockaddr*)&clientAdd, &len);
  if(clientfd <= 0)
  {
    perror("accept() error!\n");
    close(sock_fd);
    return 0;
  }

  /*接收用户发来的数据*/

  while((n = recv(clientfd, buff, 100, 0)) > 0)
  {
    buff[n] = '\n';
    printf("number of receive bytes = %d data = %s\n",n, buff);

    fflush(stdout);
    send(clientfd, buff, n, 0);
    if(strncmp(buff,"quit",4) == 0)
    break;
  }

  close(clientfd);
  close(sock_fd);
  return 0;
}

INADDR_ANY选项

   网络编程中常用到bind函数,需要绑定IP地址,这时可以设置INADDR_ANY.

  INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。
也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。 比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址了,如果某个应用程序需要监听某
个端口,那他要监听哪个网卡地址的端口呢?如果绑定某个具体的ip地址,你只能监听你所设置的ip地址所在的网
卡的端口,其它两块网卡无法监听端口,如果我需要三个网卡都监听,那就需要绑定3个ip,也就等于需要管理3个
套接字进行数据交换,这样岂不是很繁琐? 所以你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,
只要是绑定的端口号过来的数据,都可以接收到。 当然, 客户端connect时,不能使用INADDR_ANY选项。必须指明要连接哪个服务器IP。

(2)下面是echo客户端程序echo_client.c

#define ECHO_PORT 8080
#define MAX_COMMAND 5

int main()
{
  int sock_fd;
  struct sockaddr_in serv_addr;

  char *buff[MAX_COMMAND] = {"abc", "def", "test", "hello", "quit"};
  char tmp_buf[100];
  int n,i;

  /*creat socke*/
  sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  if( sock_fd == -1)
  {
    perror("creat socket error!");
    return 0;
  }
  else
  {
    printf("success to creat socket %d\n", sock_fd);
  }

  /*设置server地址结构*/
  bzero(&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(ECHO_PORT);
  serv_addr.sin_addr.s_addr = htons(INADDR_ANY);
  bzero(&(serv_addr.sin_zero), 8);

  /*链接到服务器*/
  if(-1 == connect(sock_fd, (struct sockaddr*)&serv_addr,sizeof(serv_addr)))
  {
    printf("connect error!\n");
    close(sock_fd);
    return 0;
  }
  printf("success connect to server!\n");

  /*发送并接收缓冲的数据*/
  for(i = 0; i < MAX_COMMAND; i++)
  {
    send(sock_fd, buff[i], 100, 0);
    n = recv(sock_fd, tmp_buf, 100, 0);
    tmp_buf[n] = '\n';
    printf("data send:%s receive %s\n",buff[i], tmp_buf);

    if(0 == strncmp(tmp_buf, "quit", 4))
    break;
  }
  close(sock_fd);
  return 0;
}

编译客户端和服务器端程序

[weishusheng@centOS6 echo_sock]$ gcc echo_client.c -o echo_client

[weishusheng@centOS6 echo_sock]$ gcc echo_serv.c -o echo_serv

运行服务器,输出:

[weishusheng@centOS6 echo_sock]$ ./echo_serv
success to creat socket 3
success to bind address!
success to listen!
number of receive bytes = 100 data = abc
number of receive bytes = 100 data = def
number of receive bytes = 100 data = test
number of receive bytes = 100 data = hello
number of receive bytes = 100 data = quit

在另一终端运行客户端,输出:

[weishusheng@centOS6 echo_sock]$ ./echo_client
success to creat socket 3
success connect to server!
data send: receive abc
data send:def receive def
data send:test receive test
data send:hello receive hello
data send:quit receive quit

4. socket通信十分重要,应用广泛,它还有一种通信方式,即无连接的socket通信

无连接的socket通信比较简单,它使用UDP协议,不保证数据能否到达,用在数据要求不高的地方,如在线视频。无连接的socket通信不需要建立连接,省去了维护连接的开销,所以速率更快。

无连接的socket数据报通信流程如图2

图2 无连接的socket数据报通信

和面向连接的数据流通信不同,无连接的socket数据报通信在服务器绑定socket到指定IP和端口后,没有调用listen()函数进行监听;也没有调用accept()函数对每个新的请求建立连接,因为没有连接的概念,传输层无法区分不同的连接,也就不需要对每个新的请求建立连接,在客户端创建socket后可以直接向服务器发送数据或者读取数据。

无连接的socket数据报通信发送和接收数据的函数和面向连接的套接字通信有点不同,分别使用recvfrom()和sendto()函数进行数据的发送和接收。它们的定义如下:

#include <sys/types.h>

#include <sys/socket.h>

int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

int sendto(int s, const void *msg, size_t len, int flags, struct sockaddr *to, socklen_t *tolen);

recvfrom用来从指定的IP和端口接收数据,s是socket句柄,buf是存放接收数据的缓冲首地址,len是接收缓冲大小,from是发送数据方的IP和端口号,fromlen是sockaddr结构大小。如果接收到数据,recvfrom返回接收到的字节数,失败返回-1;

sendto发送数据到指定的IP和端口,s指定socket句柄,msg是发送的数据的缓冲首地址,len是缓冲大小,to指定接收数据的IP和端口号,tolen是sockaddr结构大小。sendto()如果调用成功返回发送的字节数,失败返回-1。

无连接的时间服务通信实例

该例子服务器负责创建socket,绑定IP和端口,然后等待客户端发出请求当收到客户端的请求“time”后,生成当前时间发送给客户端。客户端创建socket后,直接向服务器发送请求时间命令,之后等待服务器返回,发送退出命令,关闭连接。

(1)服务器端,time_serv.c

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

#define TIME_PORT 9090
#define DATA_SIZE 256

int main()
{
  int sock_fd;
  struct sockaddr_in local;
  struct sockaddr_in from;
  int n;
  socklen_t fromlen;
  char buff[DATA_SIZE];
  time_t cur_time;

  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);

  if( sock_fd <= 0)
  {
  perror("creat socket error!");
  return 0;
  }
  perror("Creat socket");

  /*设置server地址结构*/
  bzero(&local, sizeof(local));
  local.sin_family = AF_INET;
  local.sin_port = htons(TIME_PORT);
  local.sin_addr.s_addr = htons(INADDR_ANY);
  //local.sin_addr.s_addr = inet_addr("192.168.1.21");
  bzero(&(local.sin_zero), 8);

  if(0 != bind(sock_fd, (struct sockaddr*)&local, sizeof(local)))
  {
    perror("bind address fail!\n");
    close(sock_fd);
    return 0;
  }
  printf("bind socket!");

  fromlen = sizeof(from);
  printf("waiting request from client...\n");

  while(1)
  {
    n = recvfrom(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&from, &fromlen);
    if(n <= 0)
    {
      perror("recv data!\n");
      lose(sock_fd);
      return 0;
    }
    buff[n]='\n';
    printf("client request:%s\n", buff);
    if(0 == strncmp(buff, "quit", 4))
    break;
    if(0 == strncmp(buff, "time", 4))
    {
      cur_time = time(NULL);
      printf("w1\n");
      // strcpy(buff, asctime(gmtime(&cur_time)));
      strcpy(buff, "weishusheng");
      printf("now time is:%s",buff);

      sendto(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&from, fromlen);
      printf("w2\n");
    }
  }
  close(sock_fd);
  return 0;

}

(2)客户端程序time_client.c

#define TIME_PORT 9090
#define DATA_SIZE 256
int main()
{
  int sock_fd;
  struct sockaddr_in serv;
  int n;
  socklen_t servlen;
  char buff[DATA_SIZE];

  sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if( sock_fd <= 0)
  {
    perror("creat socket error!");
    return 0;
  }
  perror("Creat socket");

  bzero(&serv, sizeof(serv));
  serv.sin_family = AF_INET;
  serv.sin_port = htons(TIME_PORT);
  serv.sin_addr.s_addr = htons(INADDR_ANY);
  //serv.sin_addr.s_addr = inet_addr("192.168.1.21");
  bzero(&(serv.sin_zero), 8);

  servlen = sizeof(serv);

  strcpy(buff, "time");
  if(-1 == sendto(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&serv, servlen))
  {
    perror("send data!");
    close(sock_fd);
    return 0;
  }
  printf("send time request\n");

  n = recvfrom(sock_fd, buff, sizeof(buff), 0, (struct sockaddr*)&serv, &servlen);
  printf("program goes to recvfrom()\n");
  if(n <= 0)
  {
    perror("recv data!\n");
    close(sock_fd);
    return 0;
  }
  buff[n]='\n';
  printf("time from server:%s\n", buff);

  strcpy(buff,"quit");
  if(-1 == sendto(sock_fd, buff, sizeof(buff),0 , (struct sockaddr*)&serv, servlen))
  {
    perror("send data!");
    close(sock_fd);
    return 0;
  }
  printf("send quit command\n");

  close(sock_fd);
  return 0;

}

编译运行即可看到客户端和服务器的通信过程。

进程间通信七 (socket)的更多相关文章

  1. python3 进程间通信之socket.socketpair()

    python3 进程间通信之socket.socketpair() socket.socketpair()是什么鬼东西? socket.socketpair()函数仅返回两个已经连接的套接字对象,参数 ...

  2. 通信(二):进程间通信之socket

    一.为什么要学习socket? 我们打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?我们用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket.本地的进程间通 ...

  3. Android进程间通信之socket通信

    用Java中的socket编程. 通过socket实现两个应用之间的通信,可以接收和发送数据,同时将接收到的数据显示在activity界面上. Server端: ServerLastly.java p ...

  4. Android native进程间通信实例-socket本地通信篇之——基本通信功能

    导读: 网上看了很多篇有关socket本地通信的示例,很多都是调通服务端和客户端通信功能后就没有下文了,不太实用,真正开发中遇到的问题以及程序稳定性部分没有涉及,代码健壮性不够,本系列(socket本 ...

  5. 转:Java NIO系列教程(七) Socket Channel

    Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道.可以通过以下2种方式创建SocketChannel: 打开一个SocketChannel并连接到互联网上的某台服务器. ...

  6. Android native进程间通信实例-socket本地通信篇之——服务端进程异常退出解决办法

    导读: 好难受啊,为什么服务端说挂就挂,明明只是客户端关闭而已,服务端怎么能挂呢? 想想,如果手机上使用一个聊天程序的时候,手机端关闭了聊天程序,那么远端服务器程序总不能说挂就挂吧!所以一定要查明真相 ...

  7. linux内核剖析(七)Linux进程间通信的几种方式总结

    进程间通信概述 进程通信的目的 数据传输 一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间 共享数据 多个进程想要操作共享数据,一个进程对共享数据 通知事 一个进程需要向另 ...

  8. Linux进程间通信的几种方式总结--linux内核剖析(七)

    进程间通信概述 进程通信的目的 传输数据 一个进程须要将它的数据发送给还有一个进程.发送的数据量在一个字节到几M字节之间 共享数据 多个进程想要操作共享数据,一个进程对共享数据 通知事 一个进程须要向 ...

  9. Unix/Linux进程间通信(一):概述

    序 Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的.而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进 ...

随机推荐

  1. 进程、线程、GDI+、XML、委托

    进制 表示某一位置上的数运算时是逢X进一位.二进制就是逢二进一, 十进制是逢十进一,十六进制是逢十六进一,以此类推. so:二进制001010101只有0和1计算机中的数据都是二进制表示,四进制以0. ...

  2. Hibernate从入门到精通(五)一对一单向关联映射

    上次的博文中Hibernate从入门到精通(四)基本映射我们已经讲解了一下基本映射和相关概念,接下来我们会讲稍微复杂点的映射——关系映射. 关系映射分类 关系映射即在基本映射的基础上处理多个相关对象和 ...

  3. 【BZOJ 2005】[Noi2010]能量采集

    Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量.在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起. 栋栋的植物种得 ...

  4. 分布式日志收集系统--Chukwa

    1. 安装部署 1.1 环境要求 1.使用的JDK的版本必须是1.6或者更高版本,本实例中使用的是JDK1.6 2.使用的hadoop的版本必须是Hadoop0.20.205.1及以上版本,本实例中使 ...

  5. vs中使用过的扩展和好的nuget库

    扩展 ReAttach ReAttach gives you an easy way to ReAttaching your prior debug targets. ReAttach stores ...

  6. bnuoj 27987 Record of the Attack at the Orbit (模拟)

    http://www.bnuoj.com/bnuoj/problem_show.php?pid=27987 [题意]:给定坐标输出图形 [题解]:处理坐标上的小技巧 [code]: #include ...

  7. poj 1679 The Unique MST(唯一的最小生成树)

    http://poj.org/problem?id=1679 The Unique MST Time Limit: 1000MS   Memory Limit: 10000K Total Submis ...

  8. Object调用静态方法

    谁说空指针不能调用方法 public class Foo { public static void bar() { System.out.println("bar"); } pub ...

  9. Linux下使用GDB调试程序

    问题描述:          Linux下使用GDB调试程序 问题解决:          (1)生成调试文件 注:         使用命令   gdb IOStream.c   -o IOStre ...

  10. HDU1429+bfs+状态压缩

    bfs+状态压缩思路:用2进制表示每个钥匙是否已经被找到.. /* bfs+状态压缩 思路:用2进制表示每个钥匙是否已经被找到. */ #include<algorithm> #inclu ...