c++ 实时通信系统(c++socket篇)
在上一篇简单的介绍了TCP/IP协议,在这一篇我们主要介绍socket的具体实现的函数
第一步首先我们套添加上头文件:(#pragma comment(lib, "WS2_32")这是静态的加入库文件,这里面有API函数的内容)
#include <winsock2.h>
#include<ws2tcpip.h>//定义socklen_t using namespace std; #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
第二步初始化套接字环境,(为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,)
int Ret;
WSADATA wsaData; // 用于初始化套接字环境
// 初始化WinSock环境
if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0)
{
printf("WSAStartup() failed with error %d\n", Ret);
WSACleanup(); }
第三步接下来就是socket的API函数的按顺序调用就是上一篇中的socket架构,接下来介绍一下API函数
1.socket
int socket(int domain, int type, int protocol);
SOCKET s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
int er = WSAGetLastError();
return 0;
}
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样.
创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
- domain:即协议域,又称为协议族(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_STREAM(流套接字,使用TCP协议)、SOCK_DGRAM(数据报套接字,使用UDP协议)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。我们创建的套接字都是主动的,阻塞的套接字。
如果不出错,socket函数将返回socket的描述符(句柄,一般都是大于零的),否则,将返回INVALID_SOCKET。
2.bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(27015); //----------------------
// Bind the socket.
if (bind( ListenSocket, (SOCKADDR*) &service,sizeof(service)) == SOCKET_ERROR) {
closesocket(ListenSocket);
return;
}
正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
函数的三个参数分别为:
- sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
- addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
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 */
};
//ipv6对应的是:
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 */
};
//Unix域对应的是:
#define UNIX_PATH_MAX 108 struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
}; - addrlen:对应的是地址的长度。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
无错误返回0,又错误返回SOCKET_ERROR(-1),我们可以调用WSAGetLastError来看具体的错误。
htons(host to unsigned short)和htonl(host to unsigned long)
各个机器cpu对数据存储和表示的方法不通,intel机器用littele-endian存数据,而IBM机器用big-endian存数据。网络协议为取消这种差异,一致采用big-endian方式。htons用于将unsigned short的数值从littele-endian转换为big-endian,由于short只有2字节,常用语port数值的转换。htons用于将unsigned long的数值从littele-endian转换为big-endian,long有4字节,常用于ipv4地址的转换。
1 u_short htons( __in u_short hostshort);
2 u_long htonl(__in u_long hostlong);
inet_addr和inet_ntoa
inet_addr用于将ipv4格式的字符串转换为unsigned long的数值。inet_ntoa用于将struct in_addr的地址转换为ipv4格式的字符串。
1 unsigned long inet_addr( __in const char* cp);
2 char* FAR inet_ntoa( __in struct in_addr in);
3.listen
int listen(int sockfd, int backlog);
if (listen(s,SOMAXCONN ) == SOCKET_ERROR)
{
int er = WSAGetLastError();
closesocket(s);
}
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
没有错误发生将返回0,否则返回SOCKET_ERROR .
4.connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(8828);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(cnetsocket,(sockaddr*)&server,sizeof(server)) == SOCKET_ERROR)
{
break;
}
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
0表示正确,否则,将返回SOCKET_ERROR。如果是阻塞式的socket连接,返回值代表了连接正常与失败。
5.accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
SOCKET ac =accept(listener, (struct sockaddr*)&client_address, &client_addrLength);
if (ac == INVALID_SOCKET)
{
int er = WSAGetLastError();
closesocket(s);
}
accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
如果不发生错误,accept将返回一个新的SOCKET描述符,即新建连接的socket句柄。否则,将返回INVALID_SOCKET。传进去的addrlen应该是参数addr的长度,返回的addrlen是实际长度
注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
6.send,recv
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
send的返回值标识已发送数据的长度,这个值可能比参数len小,这也意味着数据缓冲区没有全部发出去,要进行后续处理。返回SOCKET_ERROR标识send出错。
recv的返回值标识已接收数据的长度。如果连接已关闭,返回值将是0。返回SOCKET_ERROR标识recv出错。
9.closesocket
closesocket( __in SOCKET );//关闭socket
10 :getsockname 获取本地IP和PORT
Description:The getsockname function retrieves the local name for a socket.
1 int getsockname(
2 __in SOCKET s,
3 __out struct sockaddr* name,
4 __in_out int* namelen
5 );
Parameters
- s
-
Descriptor identifying a socket.
- name
-
Pointer to a SOCKADDR structure that receives the address (name) of the socket.
- namelen
-
Size of the name buffer, in bytes.
Return Value
If no error occurs, getsockname returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
11 :getpeername 获取对端IP和PORT
1 int getpeername(
2 __in SOCKET s,
3 __out struct sockaddr* name,
4 __in_out int* namelen
5 );
Getpeername和getsockname参数一样。
12.实现socket的非阻塞(在下一篇有他的用法)
nt ioctlsocket (SOCKET s, long cmd, u_long FAR* argp );
unsigned long flag=1;
if (ioctlsocket(sock,FIONBIO,&flag)!=0)
{
closesocket(sock);
return false;
} 以下是对ioctlsocket函数的相关解释:
int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);
s:一个标识套接口的描述字。
cmd:对套接口s的操作命令。
argp:指向cmd命令所带参数的指针。
具体的操作可以看https://blog.csdn.net/susubuhui/article/details/7568431
c++ 实时通信系统(c++socket篇)的更多相关文章
- iPhone socket 编程之BSD Socket篇
iPhone socket 编程之BSD Socket篇 收藏在进行iPhone网络通讯程序的开发中,不可避免的要利用Socket套接字.iPhone提供了Socket网络编程的接口CFSocket, ...
- c++ 实时通信系统(基础知识TCP/IP篇)
编写前的基础知识 C/S结构: C/S是Client/Server,即客户端/服务器端架构,一种典型的两层架构.客户端包含一个或多个在用户的电脑上运行的程序服务器端有两种,一种是数据库服务器端,客户端 ...
- 实时通讯系列目录篇之SignalR详解
一. 简单说几句 最早使用SignalR的时候大约是两年前了,记得当时是一个OA中消息的实时提醒,轮询的方式有点耗资源,WebSocket写起来又比较麻烦,最终选择了SignalR,当时是什么版本已经 ...
- 一个关于vue+mysql+express的全栈项目(五)------ 实时聊天部分socket.io
一.基于web端的实时通讯,我们都知道有websocket,为了快速开发,本项目我们采用socket.io(客户端使用socket.io-client) Socket.io是一个WebSocket库, ...
- 实时通讯之Socket.io
WebSocket WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术.使用WebSocket,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成 ...
- Win10环境下使用Flask配合Celery异步推送实时/定时消息(Socket.io)/2020年最新攻略
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_163 首先得明确一点,和Django一样,在2020年Flask 1.1.1以后的版本都不需要所谓的三方库支持,即Flask-Ce ...
- Python之路: socket篇
(默认)与特定的地址家族相关的协议,如果是 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议 sk import socketip_port = ()sk = socket.socket( ...
- 浅谈IM(InstantMessaging) 即时通讯/实时传讯【理论篇】
一.IM简要概述 IM InstantMessaging(即时通讯,实时传讯)的缩写是IM,互动百科大致解释是一种可以让使用者在网络上建立某种私人聊天(chatroom)的实时通讯服务. 大部 ...
- Python开发【socket篇】解决粘包
客户端 import os import json import struct import socket sk = socket.socket() sk.connect(('127.0.0.1',8 ...
随机推荐
- 学习Oracle数据库入门到精通教程资料合集
任何大型信息系统,都需要有数据库管理系统作为支撑.其中,Oracle以其卓越的性能获得了广泛的应用.本合集汇总了学习Oracle数据库从入门到精通的30份教程资料. 资料名称 下载地址 超详细Orac ...
- phpstrom--------config php interpreter
phpstrom是一款比较好用的php代码编辑器,使用phpstrom进行代码编辑时我可能会需要看一下在网页上的实际效果,但是PHPstrom本身只是一款编辑器,不具备运行功能,我们需要自己安装一个服 ...
- SAS数据挖掘实战篇【一】
SAS数据挖掘实战篇[一] 1数据挖掘简介 1.1数据挖掘的产生 需求是一切技术之母,管理和计算机技术的发展,促使数据挖掘技术的诞生.随着世界信息技术的迅猛发展,信息量也呈几何指数增长,如何从巨量.复 ...
- AESTest
using Gaea.MySql; using System; using System.Data; using System.IO; using System.Security.Cryptograp ...
- Django 邮箱找回密码!!!!!!!!!!!!!!!!
1.大概流程. @首先在完善登陆页面,增加忘记密码的链接. @为了账户安全,需要对操作者进行验证,向邮箱发随机数验证! @在重置验证码页面,验证验证码是否匹配(验证成功跳转至更改密码也页面). @ 重 ...
- upload上传
1>使用apache第三方控件commons-fileupload实现上传(引入jar包),能够极大的简化实现上传文件的代码量 2>能够实现文件的上传功能,当我们的项目中需要上传图片,文档 ...
- UOJ#152盘子序列
题面君 那这是一题比较标准的单调栈的题目,维护一下单调栈并访问就好了 int n;//因为我写了十几行头文件..头文件就删了,大家自己加一下吧.. ]; ],s2[],t1,t2; int get() ...
- CentOS7中yum配置
1.打开centos的yum文件夹 输入命令cd /etc/yum.repos.d/ 2.用wget下载repo文件 输入命令wget http://mirrors.aliyun.com/repo ...
- Java传统IO流和NIO流的简单对比介绍
通过对文件的拷贝来对比传统IO流和NIO流 将底层流封装成处理流以后进行分段读取. /*将本身源代码拷贝到TXT文件*/ public class TraditionIO { public stati ...
- TensorFlow实战第八课(卷积神经网络CNN)
首先我们来简单的了解一下什么是卷积神经网路(Convolutional Neural Network) 卷积神经网络是近些年逐步兴起的一种人工神经网络结构, 因为利用卷积神经网络在图像和语音识别方面能 ...