套接字—Socket

网络编程就不得不提大名鼎鼎的套接字—Socket

一,什么是Socket

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。Socket的英文原意是“插座”,通常称之为套接字,来描述IP地址和端口,是一个通信链的句柄,用来实现不同虚拟机或者计算机之间的通信。

在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,与不同客户端的不同服务对应着不同的Socket,这样实现了与多个服务器进行不同服务的功能,因为Socket的不同,这些客户端或服务不会混淆。

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。

二,套接字的类型

1. 流式套接字(SOCK_STREAM)

提供面向连接,可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接受。

2. 数据报式套接字(SOCK_DGRAM)

提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接受顺序混乱。网络文件系统(NFS)使用数据报式套接字。

3. 原始套接字(SOCK_RAW)

该接口允许对较低层协议,如IP,ICMP直接访问。常用于检验新的协议实现或者访问现在服务中配置的新设备。

三,基于TCP(有连接)/UDP(无连接)的Socket编程

要通过互联网进行通信,至少需要一对套接字,一个运行于客户机端,称之为ClientSocket,另一个运行于服务器端,称之为serverSocket。

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可分为以下几个步骤:

tcp中服务器端的流程如下:

1. 创建套接字(Socket)。

2. 将套接字绑定到一个本地地址和端口上(bind)。

3. 将套接字设为监听模式,准备接受客户请求(listen)。

4. 等待客户请求到来,当请求到来后,接受连接请求。返回一个新的对应于此次连接的套接字(accept)。

5. 用于返回的套接字和客户端进行通信(send/recv)。

6. 返回,等待另一客户请求。

7. 关闭套接字(close)。



tcp中客户端流程如下:

1. 创建套接字(socket)。

2. 向服务器发出连接请求(connect)。

3. 和服务器端进行通信(recv/send)。

4. 关闭套接字(close)。

无连接的udp服务器端流程如下:

1. 创建套接字(socket)。

2. 将套接字绑定到一个本地地址和端口上(bind)。

3. 等待接收数据(recvfrom)。

4. 关闭套接字(close)。



udp客户端流程:

1. 创建套接字(socket)。

2. 向服务器发送数据(sendto)。

3. 关闭套接字(close)。

四,相关函数说明

要运用套接字进行网络通信,有关套接字的几个重要函数是必须要掌握的,在上述TCP/UDP网络通信中出现的函数有 socket , bind , listen , accept和connect , send和recv , close。

创建套接字—socket()

应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:

#include <sys/socket.h>
int socket(int family, int type, int protocol);

该调用要接收三个参数: family、type、protocol。

参数 family指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。

参数type 描述要建立的套接字的类型。

参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。

根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。

绑定套接字—bind()

bind函数把一个本地协议地址赋予一个套接字,对于网际网协议,协议地址是32位的IPv4地址或者是128位的IPv6地址与16位的TCP或UDP端口号的组合。

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

第一个参数sockfd是函数返回的描述符。

第二个参数是一个指定特定于协议的地址结构的指针。

第三个参数是该地址结构的长度。

监听函数—listen()

listen函数仅由TCP服务器调用,它做两件事。

1. 当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listern函数把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向套接字的连接请求。

2. listen的第二个函数规定内核应该为相应套接字排队的最大连接数。

#include <sys/socket.h>
int listen(int sockfd, int backlog);

listen函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。

建立连接—accept()和connect()

accept函数由TCP服务器调用,用于从已完成连接队列对头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

参数cliaddr和addrlen用来返回已连接的对端进程的协议地址。如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。

connect函数是由TCP客户端调用,用来建立与TCP服务器的连接。

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

第二个和第三个参数就是连接服务器的套接字地址结构的指针和该结构的大小,用来将客户端的套接字与服务器套接字相连接,所以套接字地址结构必须含有服务器的IP地址和端口号。

这两个函数调用用于完成一个完整相关的建立,其中connect()用于建立连接。无连接的

套接字进程也可以调用connect(),但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端口不可操作accept()

用于使服务器等待来自某客户进程的实际连接。

数据传输—send()和recv()

send()调用用于在参数s指定的已连接的数据报或流套接字上发送输出数据,格式如下:

int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);

参数s为已连接的本地套接字描述符。buf 指向存有发送数据的缓冲区的指针,其长度

由len 指定。flags 指定传输控制方式,如是否发送带外数据等。如果没有错误发生,send()

返回总共发送的字节数。否则它返回SOCKET_ERROR。

recv()调用用于在参数s指定的已连接的数据报或流套接字上接收输入数据,格式如下:

int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);

参数s 为已连接的套接字描述符。buf指向接收输入数据缓冲区的指针,其长度由len 指

定。flags 指定传输控制方式,如是否接收带外数据等。如果没有错误发生,recv()返回总共

接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。

关闭套接字—close()

通常的UNIX close()函数用来关闭套接字,并终止TCP/UDP连接。

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

close一个TCP套接字的默认行为是把该套接字标记为关闭状态,然后立即返回到调用进程。

五,Socket(TCP / UDP)的代码实现

以下用个不同计算机之间的通信程序来演示Socket的运用,分别以有连接的TCP通信和无连接的UDP通信为例。

TCP如下:

包含的头文件和相关IP端口定义的unp.h文件:

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

#define  SERVER_IP "127.0.0.1"  //IP
#define  SERVER_PORT 7070       //端口
#define  QUEUE_SIZE  5
#define  BUFFER_SIZE 256

TCP服务器代码如下:

#include"unp.h"

void* write_fun(void *arg)    //写函数
{
    int sockConn = *(int *)arg;
    char sendbuf[BUFFER_SIZE];
    memset(sendbuf, 0, BUFFER_SIZE);
    while(1)
    {
        printf(":>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)
            break;
        send(sockConn,sendbuf, strlen(sendbuf)+1, 0);
    }
    pthread_exit(NULL);
}
void* read_fun(void *arg)   //读函数
{
    int sockConn = *(int*)arg;
    char recvbuf[BUFFER_SIZE];
    memset(recvbuf,0,BUFFER_SIZE);
    struct sockaddr_in addrCli;
    socklen_t addrlen = sizeof(struct sockaddr);
    while(1)
    {
        getsockname(sockConn, (struct sockaddr*)&addrCli, &addrlen);
        recv(sockConn,recvbuf,BUFFER_SIZE, 0);
        printf("Client[%d]:>%s\n",addrCli.sin_port,recvbuf);
    }
}

int main()
{
    int sockSer;
    sockSer = socket(AF_INET, SOCK_STREAM, 0);  //申请套接字
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer,addrCli;   //定义IP,端口
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(SERVER_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);

    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer, (struct sockaddr*)&addrSer, addrlen);  //绑定套接字
    if(res == -1)
        perror("bind");

    listen(sockSer, QUEUE_SIZE);  //监听函数

    int sockConn;
    while(1)
    {
        sockConn = accept(sockSer,(struct sockaddr*)&addrCli, &addrlen);  //接收
        if(sockConn == -1)
            perror("accept");
        else
            printf("Client[%d] Connect Server OK.\n",addrCli.sin_port);

        pthread_t tid[2];  //用线程实现实时写读
        pthread_create(&tid[0], NULL, write_fun, &sockConn);
        pthread_create(&tid[1], NULL, read_fun, &sockConn);

    }
    close(sockSer);  //关闭套接字
    return 0;
}

TCP的客户端代码如下:

#include"unp.h"

pthread_t tid[2];
void* write_fun(void *arg)
{
    int sockCli = *(int *)arg;
    char sendbuf[BUFFER_SIZE];
    memset(sendbuf, 0, BUFFER_SIZE);
    while(1)
    {
        printf(":>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)
        {
            pthread_cancel(tid[1]);
            break;
        }
        send(sockCli,sendbuf, strlen(sendbuf)+1, 0);
    }
    pthread_exit(NULL);

}
void* read_fun(void* arg)
{
    int sockCli = *(int*)arg;
    char recvbuf[BUFFER_SIZE];
    memset(recvbuf,0,BUFFER_SIZE);
    struct sockaddr_in addrCli;
    socklen_t addrlen = sizeof(struct sockaddr);
    getsockname(sockCli, (struct sockaddr*)&addrCli, &addrlen);
    while(1)
    {
        recv(sockCli,recvbuf,BUFFER_SIZE, 0);
        printf("Server:>%s\n",recvbuf);
    }
}

int main()
{
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);  //申请客户端的套接字
    if(sockCli == -1)
        perror("socket");

    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(SERVER_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);

    socklen_t addrlen = sizeof(struct sockaddr);

    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);  //连接服务器
    if(res == -1)
        perror("connect");
    else
    {
        struct sockaddr_in addrCli;
        getsockname(sockCli, (struct sockaddr*)&addrCli, &addrlen);
        printf("Client[%d] Connect Server OK.\n", addrCli.sin_port);
    }

    pthread_create(&tid[0], NULL, write_fun, &sockCli);
    pthread_create(&tid[1], NULL, read_fun, &sockCli);

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);

    close(sockCli);
    return 0;
}


无连接的UDP通信服务器代码如下(注意与TCP对比):

#include"unp.h"

int main()
{
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);  //创建套接字
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(5050);
    addrSer.sin_addr.s_addr = inet_addr("127.0.0.1");

    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];
    char recvbuf[256];
    struct sockaddr_in addrCli;
    while(1)
    {
        recvfrom(sockSer,recvbuf,256,0,(struct sockaddr*)&addrCli, &addrlen);  //注意无连接的UDP一定要先接收,才能知道客户端的地址
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);
    }
    return 0;
}

UDP客户端代码:

int main()
{
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1)
        perror("socket");

    struct sockaddr_in addrSer;
    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(5050);
    addrSer.sin_addr.s_addr = inet_addr("127.0.0.1");

    socklen_t addrlen = sizeof(struct sockaddr);

    char sendbuf[256];
    char recvbuf[256];
    struct sockaddr_in addrCli;
    while(1)
    {
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrSer, addrlen);

        recvfrom(sockCli,recvbuf,256,0,(struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n",recvbuf);
    }
    return 0;
}

Socket的套接字使用就是如上述TCP/UDP通信那样的“套路”,按顺序创建套接字,绑定,监听,接收,发送,关闭。客户端创建套接字,连接,发送,关闭。这些用到的函数是必须掌握的,这是运用Socket的关键。

在此之前,博客内已经介绍了管道通信,进程间的通信,线程间的通信,这些都是”单机“通信。今天介绍的Socket套接字通信实现了不同计算机之间的网络通信,Socket是从单机到网络的转变,Socket是互联网时代通信的基石。

然而Socket高效通信的实现方式任然需要我们认真研究,下一篇博客将介绍高效的Socket通信方式~

时间: 2024-10-14 09:49:23

套接字—Socket的相关文章

Linux进程间通信 -- 数据报套接字 socket()、bind()、sendto()、recvfrom()、close()

前一篇文章,Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用. 一.简单回顾——什么是数据报套接字 socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行.也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信.也因为这样,套接字明确地将客户端和服务器区分开来. 相对于流套接字,数据报套接字的

回写、套接字socket 、JVM映像、实际/抽象回话

1.什么是回写? 回写:更新多维数据集单元值.成员或成员属性值. 操作系统和平台上的应用程序在运行的时候需要往磁盘写入临时数据,但是在无盘环境下,没有硬盘作为操作系统和应用程序临时的交换数据空间,所以这个任务必须交给服务器来完成 计算机回写:“Write Back(回写),在回写状态下,数据只有在要被从高速缓存中清除时才写到磁盘上.随着主存读取的数据增加,回写需要开始从高速缓存中向磁盘上写数据,并把更新的数据写入高速缓存中.由于一个数据可能会被写入高速缓存中许多次,而没有进行磁盘存取,所以回写的

[python] 网络编程之套接字Socket、TCP和UDP通信实例

很早以前研究过C#和C++的网络通信,参考我的文章: C#网络编程之Tcp实现客户端和服务器聊天 C#网络编程之套接字编程基础知识 C#网络编程之使用Socket类Send.Receive方法的同步通讯 Python网络编程也类似.同时最近找工作笔试面试考察Socket套接字.TCP\UDP区别比较多,所以这篇文章主要精简了<Python核心编程(第二版)>第16章内容.内容包括:服务器和客户端架构.套接字Socket.TCP\UDP通信实例和常见笔试考题. 最后希望文章对你有所帮助,如果有不

什么是套接字(Socket)

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题.多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据.为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接. 生成套接字,主要有3个参数:通信的目的IP地址.使用的传输 层协议(TCP或UDP)和使用的端口号.Socket原意是“插座”.通过将这3个参数结合起来,与一个“

Java套接字Socket

这篇博客是本人学习<Java网络程序设计>书中第4章套接字的学习总结.初学者网友学习这篇Java套接字文章,如果难于理解文章前面理论部分,可以先运行后面的程序,边看运行后面的程序边理解前面的原理,这对初学者是最好的方法.所有源代码都在文章后面我的github链接代码中. --惠州学院 13网络工程 吴成兵 20160607 目录 1 目录 1 一 流套接字概述 二 服务器套接字ServerSocket 21 ServerSocket的工程过程 22 ServerSocket构造方法 23 Se

什么是套接字(Socket)?套接字(Socket)是什么意思?(转)

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题.多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据.为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口,区分不同应用程序进程间的网络通信和连接. 网络化的应用程序在开始任何通讯之前都必需要创建套接字.就像电话的插口一样,没有它就完全没办法通信. 生成套接字,主要有3个参数:通信的目的IP地址.使用的传输层协议(

网络---中断套接字Socket

1 package socketpack_2; 2 import java.awt.BorderLayout; 3 import java.awt.EventQueue; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.io.IOException; 7 import java.io.OutputStream; 8 import java.io.PrintWrit

网络编程 套接字socket 及 粘包

网络编程 套接字socket 及 粘包 sockt 初识 五层协议 : 从传输层包括传输层以下 , 都是操作系统帮我们封装的各种head socket套接字充当的就是内置模块的角色 socket 套接字,它存在于传输层与应用层之间的抽象层 避免你学习各层的接口以及协议的使用, socket已经封装好了所有的接口 . 直接使用这些接口或者方法即可 , 使用起来方便,提升开发效率 socket 就是一个模块 , 通过使用学习模块提供的功能 , 建立客户端与服务端的通信 套接字的工作流程(基于TCP和

专题七.网络编程之套接字Socket、TCP和UDP通信实例

https://blog.csdn.net/Eastmount/article/details/48909861 找工作笔试面试考察Socket套接字.TCP\UDP区别比较多,所以这篇文章主要精简了<Python核心编程(第二版)>第16章内容.内容包括:服务器和客户端架构.套接字Socket.TCP\UDP通信实例和常见笔试考题. https://www.cnblogs.com/alex3714/articles/5227251.html 1.Socket语法及相关 2.SocketSer