Socket编程-基础使用
最后更新:2019-10-25
一 基本概念
socket, 又称为"套接字"或者"插座". 是操作系统提供的一种进程间通信机制.目前大多用于不同网络设备之间的通信. socket 位于应用层与传输层之间, 通过传递给 socket 不同的参数, socket 最终选择不一样的协议(TCP/UDP等), 也就是说 socket 其实传输层协议簇的软件抽象.


图片来自于网络
1.1 流程概述
在网络应用中, 通信的两个进程之间主要用的是客户端/服务器 (C/S)模式, 即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务. socket 最开始设计于 Unix, Unix/Linux 设计哲学是 "一切皆文件", 因此,我们可以像文件操作(open/read/write/close)一样来操作 socket.
下图展示展示 C/S 之间如何使用 socket

对于 TCP 服务端来说:
- 创建 socket, 创建一个通信断点描述符, 但在进程中为未打开状态;
 - 绑定(bind) socket, 将 socket 绑定到某个地址以及端口上;
 - 监听(listen) socket, 用于表示该 socket 为一个被动链接(passive socket), 可以理解为 服务器的socket,需要客户端来主动连接了;
 - 接收客户端的请求(accept), 服务端通过该方法接收客户端的连接请求;
 - 读(read)/写(write)
 - 关闭 socket
 
对于客户端来说就比较的简单: 创建一个socket, 然后连接服务器(connect), 完成对应的数据操作(读写), 完成后关闭(close);
二 socket 相关接口
2.1 创建 socket()
int socket(int domain, int type, int protocol);
socket 创建返回一个 socket 描述符, 跟文件描述符类似,后面的读写都需要依赖于这个描述符.
2.1.1 参数-协议族(domain)
协议族决定了使用的 socket 地址类型;
- Unix 域 socket: 
AF_UNIX或AF_LOCAL, 使用地址类型为:struct sockaddr_un 
struct sockaddr_un {
   sa_family_t sun_family;               /* AF_UNIX */
   char        sun_path[108];            /* Pathname */
};
AF_INET, ipv4, 使用地址类型为:struct sockaddr_in
struct sockaddr_in {
   sa_family_t    sin_family; /* address family: AF_INET */
   in_port_t      sin_port;   /* port in network byte order */
   struct in_addr sin_addr;   /* internet address */
};
/* Internet address. */
struct in_addr {
   uint32_t       s_addr;     /* address in network byte order */
};
AF_INET6, ipv6, 使用地址类型为:sockaddr_in6
struct sockaddr_in6 {
   sa_family_t     sin6_family;   /* AF_INET6 */
   in_port_t       sin6_port;     /* port number */
   uint32_t        sin6_flowinfo; /* IPv6 flow information */
   struct in6_addr sin6_addr;     /* IPv6 address */
   uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
   unsigned char   s6_addr[16];   /* IPv6 address */
};
- 其他类型(
AF_X25/AF_AX25/AF_NETLINK等),其他类型目前还没有涉及到,不做过多讨论,感兴趣可以访问手册进行研究; 
2.1.2 参数-类型(type)
表示 socket 通信的类型
- SOCK_STREAM: 可靠的面向流服务或流套接字 (TCP)
 - SOCK_DGRAM: 数据报文服务或者数据报文套接字 (UDP)
 - SOCK_SEQPACKET:可靠的连续数据包服务
 - SOCK_RAW: 网络层之上自行指定运输层协议头,即原始套接字
 
2.1.3 参数-协议类型(protocol)
指定实际使用的传输协议, 传 0 表示根据前面的 domain 和 type 选择协议;
2.2 其他函数略过
其他的函数没有什么特别需要注意的地方,直接参考下面例子以及手册就能看懂
三 示例程序-本地进程间通信
当给 socket() 的协议族传入(AF_UNIX或 AF_LOCAL)时,可表示Unix域socket, 用于本地进程间通信.
其中地址类型中的 sun_path是一个值得注意的地方,根据文档所描述,有三种类型
+ pathname: 文件类型,这种文件需要服务端与客户端对文件都有操作权限, 程序执行过程中,可以看到对应的文件.
+ unnamed
+ abstract: 抽象类型, 这种类型与 pathname 区别在于,给 sun_path 的第一个值为 \0;
3.1 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
static const char *SERVERNAME = "@servername";
int main(int argc, char const *argv[])
{
    int sockfd;
    struct sockaddr_un serverAddr;
    socklen_t len;
    char buffer[1024];
    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("init fail");
        exit(1);
    }
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  
    serverAddr.sun_path[0] = '\0';
    len = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);
    if (connect(sockfd, (struct sockaddr *)&serverAddr, len) < 0)
    {
        perror("connect fail");
        exit(1);
    }
    while (fgets(buffer, sizeof(buffer), stdin) != NULL)
    {
        write(sockfd, buffer, strlen(buffer));
    }
    close(sockfd);
    return 0;
}
3.2 服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
static const char *SERVERNAME = "@servername";
int main(int argc, char const *argv[])
{
    int sockfd;
    struct sockaddr_un serverAddr;
    struct sockaddr_un clientAddr;
    socklen_t clientLen;
    socklen_t serverLen;
    char buffer[1024];
    int connfd;
    if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        perror("init fail");
        exit(1);
    }
    // 2. 设置对应地址,然后进行 connect
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVERNAME, sizeof(serverAddr.sun_path));  
    // abstract & calculate length
    serverAddr.sun_path[0] = '\0';
    serverLen = offsetof(struct sockaddr_un, sun_path) + sizeof(SERVERNAME);
    if (bind(sockfd, (struct sockaddr *)&serverAddr, serverLen) < 0)
    {
        perror("bind fail");
        exit(1);
    }
    if (listen(sockfd, 20) < 0)
    {
        perror("listen fail");
        exit(1);
    }
    while (1)
    {
        // 阻塞到有客户端链接
        if ((connfd = accept(sockfd, (struct sockaddr *)&clientAddr, &clientLen))  < 0)
        {
            printf("accept fail \r\n");
            continue;
        }
        printf("accept success \r\n");
        while(1)
        {
            memset(buffer, 0, sizeof(buffer));
            int n = read(connfd, buffer, sizeof(buffer));
            if (n < 0) {
                perror("read error");
                break;
            } else if(n == 0) {
                printf("EOF\n");
                break;
            }  
            printf("received: %s", buffer);
        }
        close(connfd);
    }
    close(sockfd);
    return 0;
}
四 实例程序-IPV4(6)网络通信
4.1 客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
    // ipv4
    int sockfd;
    struct sockaddr_in addr_in;
    char buffer[1000];
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("init socket fail");
        exit(1);
    }
    bzero(&addr_in, sizeof(addr_in));
    addr_in.sin_family = AF_INET;
    addr_in.sin_port = htons(9898);
    addr_in.sin_addr.s_addr = inet_addr("103.101.153.8");
    if ((connect(sockfd, (struct sockaddr *)&addr_in, sizeof(addr_in)))<0)
    {
        perror("connect fail");
        exit(1);
    }
    while (fgets(buffer, sizeof(buffer), stdin))
    {
        write(sockfd, buffer, strlen(buffer));
    }
    close(sockfd);
    return 0;
}
4.2 服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h> 
int main(int argc, char const *argv[])
{
    int serfd, clifd;
    struct sockaddr_in seraddr_in, cliaddr_in;
    socklen_t clilen;
    char buffer[1000];
    if ((serfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("init socket fail");
        exit(1);
    }
    bzero(&seraddr_in, sizeof(seraddr_in));
    seraddr_in.sin_family = AF_INET;
    seraddr_in.sin_port = htons(9898);
    seraddr_in.sin_addr.s_addr = inet_addr("103.101.153.8");
    if (bind(serfd, (struct sockaddr *)&seraddr_in, sizeof(seraddr_in)) < 0)
    {
        perror("bind fail");
        exit(1);
    }
    if (listen(serfd, 20) < 0)
    {
        perror("listen fail");
        exit(1);
    }
    while (1)
    {
        if ((clifd = accept(serfd, (struct sockaddr *)&cliaddr_in, &clilen)) < 0)
        {
            perror("accept fail");
            continue;
        }
        while (1)
        {
            memset(buffer, 0, sizeof(buffer));
            int len = read(clifd, buffer, sizeof(buffer));
            if (len < 0 )
            {
                perror("read fail");
                break;
            }
            if (len == 0)
            {
                perror("EOF");
                break;
            }
            printf("receive: %s", buffer);
        }
        close(clifd);
    }
    close(serfd);
    return 0;
}
对比本地与网络通信,就是选择的协议族以及对应的地址不一样而已.
参考链接:
Socket编程-基础使用的更多相关文章
- socket编程基础-字节序/IP/PORT转换/域名
		
socket编程基础 网络IP操作函数 字符串的IP和32位的IP转换 #include <sys/socket.h> #inlcude <netinet/in.h> #inc ...
 - c#socket编程基础
		
Microsoft.Net Framework为应用程序访问Internet提供了分层的.可扩展的以及受管辖的网络服务,其名字空间System.Net和System.Net.Sockets包含丰富的类 ...
 - Java从零开始学四十五(Socket编程基础)
		
一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
 - java socket编程基础(转)
		
一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
 - Python学习笔记——基础篇【第七周】———FTP作业(面向对象编程进阶 & Socket编程基础)
		
FTP作业 本节内容: 面向对象高级语法部分 Socket开发基础 作业:开发一个支持多用户在线的FTP程序 面向对象高级语法部分 参考:http://www.cnblogs.com/wupeiqi/ ...
 - 【转】Java Socket编程基础及深入讲解
		
原文:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html#q2.3.3 Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要 ...
 - 【Socket】Java Socket编程基础及深入讲解
		
Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要讲解Socket的基础编程.Socket用在哪呢,主要用在进程间,网络间通信.本篇比较长,特别做了个目录: 一.Socket通信基 ...
 - 你得学会并且学得会的Socket编程基础知识
		
这一篇文章,我将图文并茂地介绍Socket编程的基础知识,我相信,如果你按照步骤做完实验,一定可以对Socket编程有更好地理解. 本文源代码,可以通过这里下载 http://files.cnblog ...
 - Java Socket编程基础篇
		
原文地址:Java Socket编程----通信是这样炼成的 Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Sock ...
 - 【socket编程基础模板】
		
网络编程的基础是基于socket编程.socket(TCP)编程基于固定编程模板 server端: socket(声明socket类型) bind(命令socket,绑定地址和端口) listen(创 ...
 
随机推荐
- %300为0的个数(牛客第四场)--	number
			
题意: 给你一串数,问你如题. 思路: 我不是这样的作法,从后往前,先取00,再算%3==0的个数,往前推的时候有递推关系: #define IOS ios_base::sync_with_stdio ...
 - Buy a Ticket CodeForces - 938D (dijkstra)
			
大意: n节点无向图, 点$i$到点$j$的花费为$2dis(i,j)+a[j]$, 对于每个点, 求最少花费. 每条边权翻倍, 源点S向所有点$i$连边, 权为$a[i]$, 答案就为$S$到每个点 ...
 - Hadoop组成架构
			
Hadoop是apache用来“处理海量数据存储和海量数据分析”的分布式系统基础架构,更广义的是指hadoop生态圈.Hadoop的优势 高可靠性:hadoop底层维护多个数据副本,即使某个计算单元故 ...
 - FastDFS集群部署(转载 写的比较好)
			
FastDFS集群部署 之前介绍过关于FastDFS单机部署,详见博文:FastDFS+Nginx(单点部署)事例 下面来玩下FastDFS集群部署,实现高可用(HA) 服务器规划: 跟踪服务器1 ...
 - whistle 前端工具之抓包利器
			
一.业务场景 前端本地开发的场景中,我们需要频繁的改动代码,并需要实时看到效果,并且在一些开发场景中,我们需要将特定的请求代理到特定的IP.本地文件等,所以使用fiddler或whistle等本地.真 ...
 - JS中的事件传播流程
			
JS中的事件传播流程 1,Javascript与HTML之间的交互是通过事件实现的. 事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间. 可以使用侦听器来预定事件,以便事件发生时执行相应代码. 2 ...
 - js特效背景--点线随着鼠标移动而改变
			
https://blog.csdn.net/css33/article/details/89450852 https://www.cnblogs.com/qq597585136/p/7019755.h ...
 - Docker 环境下部署 redash
			
环境: centos7 官网:https://redash.io/help/open-source/dev-guide/docker 一.安装步骤 1.虚拟机安装 安装vmware,并安装centos ...
 - Go语法的基本使用(三)
			
// 长度 vs 容量. // 长度是目前里面有几个值 // 容量是最多能放多少个值 func main(){ var a =make(chan int,4) a<-1 a<-2 a< ...
 - 记录一次维护weblogic集群的问题
			
[问题描述] weblogic 集群子服务节点启动,启动完毕后状态改为 ADMIN,正常情况是 RUNNING 在启动脚本添加如下配置即可 -DDomainRegistrationEnabled=tr ...