参考文档
https://en.wikipedia.org/wiki/Network_socket#Socket_states_in_the_client-server_model
https://en.wikipedia.org/wiki/Berkeley_sockets
http://blog.csdn.net/zapldy/article/details/5813984
Socket 是一组接口,它把复杂的TCP/IP协议族隐藏在Socket接口后面。
image.png1、对比 Socket 在哪里
image.png
Socket是什么呢?
: Socket是应用层与TCP/IP协议族通信的中间软件抽象层
,它是一组接口
.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族
隐藏在Socket接口后面。对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.
image.png
先从Server
端说起.
Server
服务器端先初始化Socket,
Server
然后与端口绑定(bind),
Server
对端口进行监听(listen),
Server
调用accept阻塞,等待客户端连接.
Client
在这时如果有个客户端初始化一个Socket,
Client
然后连接服务器(connect)
=====>
如果连接成功,这时客户端与服务器端的连接就建立了.
=====>
Client
客户端发送数据请求,
Server
服务器端接收请求并处理请求,然后把回应数据发送给客户端,
Client
客户端读取数据,
最后关闭连接,一次交互结束.
QQ20180129-191303@2x.png QQ20180129-191337@2x.png pasted-image.pngSocket 编程
导入头文件.pngFlow diagram of client-server transaction using sockets with the Transmission Control Protocol (TCP).
This list is a summary of functions or methods provided by the
Berkeley sockets API library:
- socket() creates a new socket of a certain socket type, identified by an integer number, and allocates system resources to it.
- bind() is typically used on the server side, and associates a socket with a socket address structure, i.e. a specified local port number and IP address.
- listen() is used on the server side, and causes a bound TCP socket to enter listening state.
- connect() is used on the client side, and assigns a free local port number to a socket. In case of a TCP socket, it causes an attempt to establish a new TCP connection.
- accept() is used on the server side. It accepts a received incoming attempt to create a new TCP connection from the remote client, and creates a new socket associated with the socket address pair of this connection.
- send() and recv(), or write() and read(), or sendto() and recvfrom(), are used for sending and receiving data to/from a remote socket.
- close() causes the system to release resources allocated to a socket. In case of TCP, the connection is terminated.
- gethostbyname() and gethostbyaddr() are used to resolve host names and addresses. IPv4 only.
- select() is used to suspend, waiting for one or more of a provided list of sockets to be ready to read, ready to write, or that have errors.
- poll() is used to check on the state of a socket in a set of sockets. The set can be tested to see if any socket can be written to, read from or if an error occurred.
- getsockopt() is used to retrieve the current value of a particular socket option for the specified socket.
- setsockopt() is used to set a particular socket option for the specified socket.
//
// main.m
#import <Foundation/Foundation.h>
#import <arpa/inet.h>
static const short SERVER_PORT = 1234; // 端口
static const int MAX_Q_LEN = 64; // 最大队列长度
static const int MAX_MSG_LEN = 4096; // 最大消息长度
// 字符串截断函数
void change_enter_to_tail_zero(char * const buffer, int pos) {
for (int i = pos - 1; i >= 0; i--) {
if (buffer[i] == '\r') {
buffer[i] = '\0';
break;
}
}
}
// 处理客户端的内容。
void handle_client_connection(int clientSocketFD) {
bool clientConnected = true;
while (clientConnected) {
char buffer[MAX_MSG_LEN + 1]; // C语言的数组结束符是'\0' 所以数组的实际长度是 MAX_MSG_LEN + 1
/**
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
param 客户端套接字文件句柄
param buffer 存放接受信息的缓冲区
param 1 让内存进行分配buffer-1个字节空间(注意:计量单位是字节)
param 0 一般设置为0
return 如果是-1表示没有成功接收到客户端套接字的信息,否则就是成功接收到了信息
*/
ssize_t bytesToRecv = recv(clientSocketFD, buffer, sizeof buffer - 1, 0); //buffer - 1 是因为 bytesToRecv的信息存贮空间大小是 MAX_MSG_LEN
if (bytesToRecv > 0) {
buffer[bytesToRecv] = '\0'; //把buffer字符数组最后一个设置为\0 “字符串结束符” 注意:数组角标是0开始的。
change_enter_to_tail_zero(buffer, (int)bytesToRecv); //1.字符串的截断。通过在字符数组后面添加 '\0'即可。
printf("%s\n", buffer);
if (!strcmp(buffer, "bye")) { //通过匹配 bye 字符串来关闭链接。
clientConnected = false;
}
/**
不论是客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据
param 客户发送端套接字的描述符
param buffer存放要发送数据的缓冲去
param 实际要发送数据的字节数
param 0 一般做保留
return 如果是-1表示没有成功发送到信息,否则就是成功发送信息
*/
ssize_t bytesToSend = send(clientSocketFD, buffer,
bytesToRecv, 0);
if (bytesToSend > 0) {
printf("Echo message has been sent.\n");
}
}
else { //如果接受到内容,说明链接失败
printf("client socket closed!\n");
clientConnected = false;
}
}
close(clientSocketFD); //关闭客户端Soket
}
int main() {
/**
创建一个socket对象,什么是socket:网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket;
param AF_INET 指定使用IPV4协议
param SOCK_STREAM 指定使用TCP协议
param AF_UNIX 指定使用本地的socket
return 如果返回的是-1,表示创建失败,否则表示创建成功,该值是一个socket的描述符 一般用命名使用FD作为结尾,起源于Unix “everything is a file”的设计哲学。
*/
int serverSocketFD = socket(AF_INET, SOCK_STREAM, 0); //创建一个服务端socket对象
if (serverSocketFD < 0) { // 创建socket文件句柄失败
NSLog(@"%d", serverSocketFD);
perror("无法创建套接字!!!\n");
exit(1);
}
// 宏资料的地址:https://opensource.apple.com/source/xnu/xnu-1228/libkern/libkern/_OSByteOrder.h
/*
#define ntohs(x) __DARWIN_OSSwapInt16(x) // 16位整数 网络字节序转主机字节序
#define htons(x) __DARWIN_OSSwapInt16(x) // 16位整数 主机字节序转网络字节序
#define ntohl(x) __DARWIN_OSSwapInt32(x) //32位整数 网络字节序转主机字节序
#define htonl(x) __DARWIN_OSSwapInt32(x) //32位整数 主机字节序转网络字节序
*/
// 定义一个socket地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET; // 指定IPV4
serverAddr.sin_port = htons(SERVER_PORT); // 设置端口
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY 设置监听所有的地址。这里我们使用127.0.0.1.(使用telent命令进行测试后,1234端口被占用,可以通过 kill pid 来关闭占用该接口的进程。)
// 3. 把socket address接口绑定到系统的socket句柄上。
int ret = bind(serverSocketFD, (struct sockaddr *)&serverAddr, sizeof serverAddr);
if (ret < 0) {
perror("无法将套接字绑定到指定的地址!!!\n");
close(serverSocketFD);
exit(1);
}
/**
serverSocketFD监听通过sreverAddr进入socket的TCP链接(该TCP前面通过SOCK_STREAM指定了。)
@param serverSocketFD serverSocket的文件表述
@param MAX_Q_LEN 监听队列上的最大监听队列长度
@return 是否成功开启监听
*/
ret = listen(serverSocketFD, MAX_Q_LEN);
if (ret < 0) {
perror("无法开启监听!!!\n");
close(serverSocketFD);
exit(1);
}
//5.设定一个死循环,让服务端一直处于开启状态。
while(true) {
struct sockaddr_in clientAddr; //创建一个socket客户端地址
socklen_t clientAddrLen = sizeof clientAddr; // 设定socket客户端地址长度
/**
该函数为每一个TCP链接创建一个新的套接字,之后从监听队列上移除该链接请求。
param serverSocketFD 服务端的socket文件句柄
param &clientAddr 客户端socket的地址
return 客户端的socket文件对象句柄,注意该客户端socket对象是通过sockaddr_in和accept俩进行创建的。
*/
int clientSocketFD = accept(serverSocketFD, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (clientSocketFD < 0) {
perror("接受客户端连接时发生错误!!!\n");
} else {
//在子线程中异步执行客户端的socket。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
handle_client_connection(clientSocketFD);
});
}
}
return 0;
}
网友评论