1. socket概述

socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

2.接口详解

socket():创建socket

bind():绑定socket到本地地址和端口,通常由服务端调用

listen():TCP专用,开启监听模式

accept():TCP专用,服务器等待客户端连接,一般是阻塞态

connect():TCP专用,客户端主动连接服务器

send():TCP专用,发送数据

recv():TCP专用,接收数据

sendto():UDP专用,发送数据到指定的IP地址和端口

recvfrom():UDP专用,接收数据,返回数据远端的IP地址和端口

closesocket():关闭socket

2.1 socket()

原型:int socket (int domain, int type, int protocol)

功能描述:初始化创建socket对象,通常是第一个调用的socket函数。 成功时,返回非负数的socket描述符;失败是返回-1。socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用socket()函数时,socket执行体将建立一个socket,实际上"建立一个socket"意味着为一个socket数据结构分配存储空间。socket执行体为你管理描述符表。

参数解释

domain -- 指明使用的协议族。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type -- 指明socket类型,有3种:

SOCK_STREAM -- TCP类型,保证数据顺序及可靠性;

SOCK_DGRAM --  UDP类型,不保证数据接收的顺序,非可靠连接;

SOCK_RAW -- 原始类型,允许对底层协议如IP或ICMP进行直接访问,不太常用。

protocol -- 通常赋值"0",由系统自动选择。

2.2 bind()

原型:int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen)

功能描述:将创建的socket绑定到指定的IP地址和端口上,通常是第二个调用的socket接口。返回值:0 -- 成功,-1 -- 出错。当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。

通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

注意

(1) 如果有多个可用的连接(多个IP),内核会根据优先级选择一个IP作为源IP使用。

(2) 如果socket使用bind绑定到特定的IP和port,则无论是TCP还是UDP,都会从指定的IP和port发送数据。

参数解释

sockfd -- socket()函数返回的描述符;

myaddr -- 指明要绑定的本地IP和端口号,使用网络字节序,即大端模式(详见3.1)。

addrlen -- 常被设置为sizeof(struct sockaddr)。

可以利用下边的赋值语句,自动绑定本地IP地址和随机端口:

my_addr.sin_port = ;    /* 系统随机选择一个未被使用的端口号 */
my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */

另外要注意的是,当调用函数时,一般不要将端口号置为小于1024的值,因为1~1024是保留端口号,你可以使用大于1024中任何一个没有被占用的端口号。

2.3 listen()

原型:int listen(int sockfd, int backlog)

功能描述:listen()函数仅被TCP类型的服务器程序调用,实现监听服务,它实现2件事情:

“1. 当socket()创建1个socket时,被假设为主动式套接字,也就是说它是一个将调用connect()发起连接请求的客户端套接字;函数listen()将套接口转换为被动式套接字,指示内核接受向此套接字的连接请求,调用此系统调用后tcp 状态机由close转换到listen。
   2.第2个参数指定了内核为此套接字排队的最大连接个数。”

listen()成功时返回0,错误时返回-1。

参数解释

sockfd -- socket()函数返回的描述符;

backlog -- 指定内核为此套接字维护的最大连接个数,包括“未完成连接队列--未完成3次握手”、“已完成连接队列--已完成3次握手,建立连接”。大多数系统缺省值为20。

2.4 accept()

原型: int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen)

功能描述:accept()函数仅被TCP类型的服务器程序调用,从已完成连接队列返回下一个建立成功的连接,如果已完成连接队列为空,线程进入阻塞态睡眠状态。成功时返回套接字描述符,错误时返回-1。

如果accpet()执行成功,返回由内核自动生成的一个全新socket描述符,用它引用与客户端的TCP连接。通常我们把accept()第一个参数成为监听套接字(listening socket),把accept()功能返回值成为已连接套接字(connected socket)。一个服务器通常只有1个监听套接字,监听客户端的连接请求;服务器内核为每一个客户端的TCP连接维护1个已连接套接字,用它实现数据双向通信。

参数解释

sockfd -- socket()函数返回的描述符;

addr -- 输出一个的sockaddr_in变量地址,该变量用来存放发起连接请求的客户端的协议地址;

addrten -- 作为输入时指明缓冲器的长度,作为输出时指明addr的实际长度。

2.5 connetct()

原型: int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)

功能描述:connect()通常由TCP类型客户端调用,用来与服务器建立一个TCP连接,实际是发起3次握手过程,连接成功返回0,连接失败返回1。

注意

(1) 可以在UDP连接使用使用connect(),作用是在UDP套接字中记住目的地址和目的端口。
(2) UDP套接字使用connect后,如果数据报不是connect中指定的地址和端口,将被丢弃。没有调用connect的UDP套接字,将接收所有到达这个端口的UDP数据报,而不区分源端口和地址。

参数解释

sockfd -- 本地客户端额socket描述符;

serv_addr -- 服务器协议地址;

addrlen -- 地址缓冲区的长度。

2.6 send()

原型:int send(int sockfd, const void *msg, int len, int flags)

功能描述:TCP类型的数据发送。

每个TCP套接口都有一个发送缓冲区,它的大小可以用SO_SNDBUF这个选项来改变。调用send函数的过程,实际是内核将用户数据拷贝至TCP套接口的发送缓冲区的过程:若len大于发送缓冲区大小,则返回-1;否则,查看缓冲区剩余空间是否容纳得下要发送的len长度,若不够,则拷贝一部分,并返回拷贝长度(指的是非阻塞send,若为阻塞send,则一定等待所有数据拷贝至缓冲区才返回,因此阻塞send返回值必定与len相等);若缓冲区满,则等待发送,有剩余空间后拷贝至缓冲区;若在拷贝过程出现错误,则返回-1。关于错误的原因,查看errno的值。

如果send在等待协议发送数据时出现网络断开的情况,则会返回-1。注意:send成功返回并不代表对方已接收到数据,如果后续的协议传输过程中出现网络错误,下一个send便会返回-1发送错误。TCP给对方的数据必须在对方给予确认时,方可删除发送缓冲区的数据。否则,会一直缓存在缓冲区直至发送成功(TCP可靠数据传输决定的)。

参数解释

sockfd -- 发送端套接字描述符(非监听描述符)。

msg -- 待发送数据的缓冲区。

len -- 待发送数据的字节长度。

flags -- 一般情况下置为0。

2.7 recv()

原型:int recv(int sockfd, void *buf, int len, unsigned int flags)

功能描述:TCP类型的数据接收。

recv()从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1。

参数解释

sockefd -- 接收端套接字描述符(非监听描述符);

buf -- 接收缓冲区的基地址;

len -- 以字节计算的接收缓冲区长度;

flags -- 一般情况下置为0。

2.8 sendto()

原型:int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *dst_addr, int addrlen)

功能描述:用于非可靠连接(UDP)的数据发送,因为UDP方式未建立连接socket,因此需要制定目的协议地址。

当本地与不同目的地址通信时,只需指定目的地址,可使用同一个UDP套接口描述符sockfd,而TCP要预先建立连接,每个连接都会产生不同的套接口描述符,体现在:客户端要使用不同的fd进行connect,服务端每次accept产生不同的fd。

因为UDP没有真正的发送缓冲区,因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误ENOBUFS.

参数解释

sockfd -- 发送端套接字描述符(非监听描述符);

msg -- 待发送数据的缓冲区;

len -- 待发送数据的字节长度;

flags -- 一般情况下置为0;

dst_addr -- 数据发送的目的地址;

addrlen -- 地址长度。

2.9 recvfrom()

原型:int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, int*fromlen)

功能描述:用于非可靠连接(UDP)的数据接收。

参数解释

sockfd -- 接收端套接字描述;

buf -- 用于接收数据的应用缓冲区地址;

len -- 指名缓冲区大小;

flags -- 通常为0;

src_addr -- 数据来源端的地址;

fromlen -- 作为输入时,fromlen常置为sizeof(struct sockaddr);当输出时,fromlen包含实际存入buf中的数据字节数。

3.TCP例程

给出1个TCP类型socket通信的源代码,运行于windows平台。

3.1服务器端源码

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(,);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=)
{
return ;
} //创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return ;
} //绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons();
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
} //开始监听
if(listen(slisten, ) == SOCKET_ERROR)
{
printf("listen error !");
return ;
} //循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //接收数据
int ret = recv(sClient, revData, , );
if(ret > )
{
revData[ret] = 0x00;
printf(revData);
} //发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), );
closesocket(sClient);
} closesocket(slisten);
WSACleanup();
return ;
}

3.2 客户端源码

#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(,);
WSADATA data;
if(WSAStartup(sockVersion, &data) != )
{
return ;
} SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return ;
} sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons();
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return ;
}
char * sendData = "你好,TCP服务端,我是客户端!\n";
send(sclient, sendData, strlen(sendData), ); char recData[];
int ret = recv(sclient, recData, , );
if(ret > )
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return ;
}

4.UDP例程

4.1 服务器端源码

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h> #pragma comment(lib, "ws2_32.lib") int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(,);
if(WSAStartup(sockVersion, &wsaData) != )
{
return ;
} SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(serSocket == INVALID_SOCKET)
{
printf("socket error !");
return ;
} sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons();
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return ;
} sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
char recvData[];
int ret = recvfrom(serSocket, recvData, , , (sockaddr *)&remoteAddr, &nAddrLen);
if (ret > )
{
recvData[ret] = 0x00;
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
printf(recvData);
} char * sendData = "一个来自服务端的UDP数据包\n";
sendto(serSocket, sendData, strlen(sendData), , (sockaddr *)&remoteAddr, nAddrLen); }
closesocket(serSocket);
WSACleanup();
return ;
}

4.2 客户端源码

#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h> #pragma comment(lib, "ws2_32.lib") int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(,);
WSADATA wsaData;
if(WSAStartup(socketVersion, &wsaData) != )
{
return ;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons();
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin); char * sendData = "来自客户端的数据包.\n";
sendto(sclient, sendData, strlen(sendData), , (sockaddr *)&sin, len); char recvData[];
int ret = recvfrom(sclient, recvData, , , (sockaddr *)&sin, &len);
if(ret > )
{
recvData[ret] = 0x00;
printf(recvData);
} closesocket(sclient);
WSACleanup();
return ;
}

5. 参考资料

1. 常用socket函数详解

2. 网络编程socket基本API详解

3. VC++ socket通信实例详解

socket接口详解的更多相关文章

  1. socket原理详解

    1.什么是socket 我们知道进程通信的方法有管道.命名管道.信号.消息队列.共享内存.信号量,这些方法都要求通信的两个进程位于同一个主机.但是如果通信双方不在同一个主机又该如何进行通信呢?在计算机 ...

  2. (转)python标准库中socket模块详解

    python标准库中socket模块详解 socket模块简介 原文:http://www.lybbn.cn/data/datas.php?yw=71 网络上的两个程序通过一个双向的通信连接实现数据的 ...

  3. Linux的SOCKET编程详解(转)

    Linux的SOCKET编程详解 1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系 ...

  4. 常用socket函数详解

    常用socket函数详解 关于socket函数,每个的意义和基本功能都知道,但每次使用都会去百度,参数到底是什么,返回值代表什么意义,就是说用的少,也记得不够精确.每次都查半天,经常烦恼于此.索性都弄 ...

  5. 【ARM-Linux开发】Linux的SOCKET编程详解

    Linux的SOCKET编程详解 1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系 ...

  6. JDBC常用接口详解

    JDBC中常用接口详解 ***DriverManager 第一.注册驱动 第一种方式:DriverManager.registerDriver(new com.mysql.jdbc.Driver()) ...

  7. Socket 死连接详解

    Socket 死连接详解 当使用 Socket 进行通信时,由于各种不同的因素,都有可能导致死连接停留在服务器端,假如服务端需要处理的连接较多,就有可能造成服务器资源严重浪费,对此,本文将阐述其原理以 ...

  8. windows socket函数详解

    windows socket函数详解 近期一直用第三方库写网络编程,反倒是遗忘了网络编程最底层的知识.因而产生了整理Winsock函数库的想法.以下知识点均来源于MSDN,本人只做翻译工作.虽然很多前 ...

  9. Java6.0中Comparable接口与Comparator接口详解

    Java6.0中Comparable接口与Comparator接口详解 说到现在,读者应该对Comparable接口有了大概的了解,但是为什么又要有一个Comparator接口呢?难道Java的开发者 ...

随机推荐

  1. oletools下载安装及rtfobj使用

    rtf内嵌对象分析提取工具rtfobj是oletools的一部分 oletools各个版本下载地址https://bitbucket.org/decalage/oletools/downloads/ ...

  2. 牛客网暑期ACM多校训练营(第七场)J题(Sudoku Subrectangles)题解

    一.题意 给定一个高度为$n$,宽度为$m$的字母矩形(有大写和小写字母,共$52$种),问里面有多少个子矩形是“数独矩形”.数独矩形是指,该矩形内每一行内元素都不相同,每一列内元素也不相同. 二.思 ...

  3. Doris FE负载均衡配置

    0 背景概述 Doris完全兼容了mysql协议,并且Doris FE本身通过多follower选举机制选举出master,可以保证fe本身的高可用性,也可以通过加入observer fe节点来提高f ...

  4. Python日志配置类

    # -*- coding: utf-8 -* """日志工具类 author: Jill usage: from common.logger import Log log ...

  5. 有关Firefox/Chrome的问题汇总

    安装的附加组件因未经验证而被 Firefox 禁用,我该怎么办 如果您已安装的附加组件因未经验证而被禁用了,建议您联系附加组件开发者或提供给您附加组件的人,看看他们能不能提供新版经过验证的附加组件.您 ...

  6. C# WEB.API 多图上传

    using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Ne ...

  7. JSP生成WORD文档,EXCEL文档及PDF文档的方法

    转自:https://www.jb51.net/article/73528.htm 本文实例讲述了JSP生成WORD文档,EXCEL文档及PDF文档的方法.分享给大家供大家参考,具体如下: 在web- ...

  8. jsfl 发布保存关闭

    fl.getDocumentDOM().publish(); fl.getDocumentDOM().save(); fl.getDocumentDOM().close();

  9. as3 air 保存文本内容的换行

    private function newData() { var str:String="<root>"; for(var i in charCode_arr) { s ...

  10. mime设置

    ie9对mime有特殊要求,必须要有type才可以. 如果出现css的mime类型不支持.则没有加 type="css/text" 查看本机的mime支持: regedit > ...