1.什么是Socket
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
Socket起源于Unix,而Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式 来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。程序必须绑定一个端口才可以从网络上收发数据,这样,远程发到这个端口上的数据,就全会转给这个程序。
2.分类
为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字。
(1)流式套接字。它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。
(2)数据报套接字。它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。
(3)原始套接字。该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现。
3.用到的API
server用到的API有socket,bind,listen,accept,read,write,close (read和write可以用send和recv替换)
client用到的API有socket,connect,read,write,close (read和write可以用send和recv替换)
通信流程如下:
1)服务端创建socket
2)绑定socket和端口号
3)监听该端口号
4)启动accept()用来接收来自客户端的连接请求,此时如果有连接则继续执行,否则将阻塞在这里。
5)客户端创建socket
6)客户端通过IP地址和端口号连接服务端,即tcp中的三次握手
7)连接成功,客户端可以向服务端发送数据
8)服务端读取客户端发来的数据
9)任何一端均可主动断开连接
4.Socket编程
服务器端:
#include <stdio.h> #include <iostream> #include <winsock2.h> #include <WS2tcpip.h> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if(listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } //循环接收数据 SOCKET sClient; sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); char revData[255]; while (true) { printf("等待连接...\n"); sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { printf("accept error !"); continue; } printf("接受到一个连接:%s \r\n", inet_ntop(AF_INET, (void*)&remoteAddr.sin_addr, revData, 16)); // printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); //inet_ntoa老函数在新版本VS+64位计算机上使用会报错 //接收数据 int ret = recv(sClient, revData, 255, 0); if(ret > 0) { revData[ret] = 0x00; printf(revData); } //发送数据 const char * sendData = "你好,TCP客户端!\n"; send(sClient, sendData, strlen(sendData), 0); closesocket(sClient); } closesocket(slisten); WSACleanup(); return 0; }
客户端:
#include<WINSOCK2.H> #include<STDIO.H> #include<iostream> #include<cstring> #include <WS2tcpip.h> using namespace std; #pragma comment(lib, "ws2_32.lib") #pragma warning(disable:4996) int main() { WORD sockVersion = MAKEWORD(2, 2); WSADATA data; if(WSAStartup(sockVersion, &data)!=0) { return 0; } while(true){ SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sclient == INVALID_SOCKET) { printf("invalid socket!"); return 0; } sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); //serAddr.sin_addr.S_un.S_addr = inet_pton(AF_INET, "127.0.0.1", &serAddr); 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 0; } string data; cin>>data; const char * sendData; sendData = data.c_str(); //string转const char* //char * sendData = "你好,TCP服务端,我是客户端\n"; send(sclient, sendData, strlen(sendData), 0); //send()用来将数据由指定的socket传给对方主机 //int send(int s, const void * msg, int len, unsigned int flags) //s为已建立好连接的socket,msg指向数据内容,len则为数据长度,参数flags一般设0 //成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error char recData[255]; int ret = recv(sclient, recData, 255, 0); if(ret>0){ recData[ret] = 0x00; printf(recData); } closesocket(sclient); } WSACleanup(); return 0; }
1)socket()函数
Linux中函数形式为:
int socket(int domain, int type, int protocol);
domain即协议域,又称为协议族(family)。对TCP/IP协议族而言,该参数应该设置为PF_INET或PF_INET6,分别对应IPv4和IPv6.
type用来指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM
protocol指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP
C++中:
SOCKET PASCAL FAR socket(int af, int type, int protocol);
2)bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind将my_addr所指的socket地址分配给未命名的socketfd文件描述符,addrlen参数指出该socket地址的长度。bind成功时返回0,失败则返回-1并设置errno,常见为EACCES和EASSRINUSE,前者代表被绑定的地址是受保护的地址,仅超级用户能够访问,后者表示被绑定的地址正在使用中。
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 */ };
在c++下,函数形式为
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
3)listen()、connect()函数
int listen(int sockfd, int backlog); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
对于服务器来说,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
listen()函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket队列中允许的连接数目。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect()函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度,客户端通过三次握手来建立与TCP服务器的连接。
在c++下,函数形式为
int PASCAL FAR listen(SOCKET s, int backlog); int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);
listen()中,参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。
connect()中,参数s是欲建立连接的本地套接字描述符,参数name指出说明对方套接字地址结构的指针,对方套接字地址长度由namelen说明。
4)accept()函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数接收请求,这样连接就建立好了,之后就可以开始I/O操作了。
accept()函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是一个全新的描述字,返回客户的TCP连接。
在c++中,函数形式为
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
参数s为本地套接字描述符,在用accept()调用参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen 为客户方套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回INVALID_SOCKET。
5)send()、recv()函数
int send(int sockfd, const void *msg, int len, int flags); int recv(int sockfd, void *buf, int len, unsigned int flags);
在c++下,函数形式为
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags); int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);
6)close()函数
int close(int fd);
当完成了读写操作就要关闭相应的socket描述字,此时用close()函数。
在c++下,函数形式为
int PASCAL FAR closesocket ( IN SOCKET s);
原文地址:https://www.cnblogs.com/ft-97/p/12024908.html