原理
原文链接
一、什么是TCP/IP?
TCP提供基于IP环境下的数据可靠性传输,事先需要进行三次握手来确保数据传输的可靠性。详细的博主不再赘述,感兴趣的朋友可以去search一下。
二、什么是socket?
socket顾名思义就是套接字的意思,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。
socket编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW),前两者较常用。基于TCP的socket编程是流式套接字。
三、client/server即C/S模式:
TCP/IP通信中,主要是进行C/S交互。废话不多说,下面看看具体交互内容:
服务端:建立socket,申明自身的port和IP,并绑定到socket,使用listen监听,然后不断用accept去查看是否有连接。如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket。如果不需要等待任何客户端连接,那么用closeSocket直接关闭自身的socket。
客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。
细节
1、server端
1)加载套接字库,创建套接字(WSAStartup()/socket());
{ WSADATA ws;
WSAStartup(MAKEWORD(2, 2), &ws);
char *clnt_buf;
char recv_buf[MAX];
memset(recv_buf, 'k', MAX);
ssize_t ret;
clnt_buf = malloc(SIZE);
memset(clnt_buf, 'M', SIZE);
}
start_socket();
void start_socket() {
if ((orig_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("generate error");
}
//创建套接字 s_server = socket(AF_INET, SOCK_STREAM, 0);
SOCKET WSAAPI socket(
int af, //地址族规范。 地址族的可能值在Winsock2.h头文件中定义。
int type,//新套接字的类型规范。
int protocol//要使用的协议。
);
memset函数及其用法(最全详解)
void *memset(void s, int ch, size_t n);:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 [1] 。
char a[20]清零,一定是 memset(a,0,20sizeof(char));
malloc()分配所需的内存空间,并返回一个指向它的指针。char *clnt_buf;clnt_buf = malloc(SIZE);
ssize_t是有符号整型,在32位机器上等同与int,在64位机器上等同与long int。ssize_t ret;
(2)绑定套接字到一个IP地址和一个端口上(bind());
static struct sockaddr_in clnt_adr, serv_adr;
void start_bind() {
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(PORT);
if (bind(orig_sock, (struct sockaddr *) &serv_adr,
sizeof(serv_adr)) < 0) {
perror("bind error");
clean_up(orig_sock, NAME);
}
}
三个结构体 struct sockaddr_in, struct sockaddr, struct in_addr
/* socket通用地址 */
struct sockaddr {
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14字节的协议地址*/
};
本次用到的网络套接字地址
/* Internet socket 地址*/
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */
};
大佬蔑视之由 serverAdd.sin_addr.s_addr 引发的思考
typedef struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
} in_addr;
最后,struct in_addr就是32位IP地址
struct in_addr {
unsigned long s_addr;
};
ntohs, ntohl, htons,htonl的比较和详解
htons()
将主机的无符号短整形数
转换成网络字节顺序。//将无符号短整型主机字节序转换为网络字节序
htonl()
将主机的无符号长整形数
转换成网络字节顺序。//将无符号长整型网络字节序转换为主机字节序
均返回一个网络字节顺序的值。
int bind(
int sockfd, (1)参数 sockfd ,需要绑定的socket。
const struct sockaddr *addr, (2)参数 addr ,存放了服务端用于通信的地址和端口。
socklen_t addrlen (3)参数 addrlen ,表示 addr 结构体的大小
);(4)返回值:成功则返回0 ,失败返回-1,错误原因存于 errno 中。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。
void perror(const char *str):str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。
(3)将套接字设置为监听模式等待连接请求(listen());
void start_listen() {
listen(orig_sock, 5);
}
socket编程:listen()函数详解
int listen(
In SOCKET s, //已绑定但未监听的套接字
In int backlog //挂起连接队列的最大长度
);//监听套接字标识符
成功返回0
失败返回SOCKET_ERROR
具体错误码:WSAGetLastError()
释放:closesocket(socketListen);
清理:WSACleanup();
(4)请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
void start_accept() {
clnt_len = sizeof(clnt_adr);
if ((new_sock = accept(orig_sock, (struct sockaddr *) &clnt_adr, &clnt_len)) < 0) {
perror("accept error");
clean_up(orig_sock, NAME);
}
}
sizeof是C语言的一种单目操作符,如C语言的其他操作符++、–等。
sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。它并不是函数。
sizeof()运算符的值的类型是size_t而不是int。然后在printf里用%zu
作为size_t的格式。32位可以用%d 或 %u,64位可以用%lld 或 %llu
C语言:关于sizeof返回值的理解
SOCKET WSAAPI accept(
SOCKET s, // s包含的是服务器的ip和port信息 。
sockaddr *addr, //addr用于存放客户端的地址
int *addrlen //addrlen在调用函数时被设置为addr指向区域的长度,在函数调用结束后被设置为实际地址信息的长度。
);//返回值是一个新的套接字描述符
(5)用返回的套接字和客户端进行通信(send()/recv());
for (;;) {
// int flags = fcntl(new_sock, F_GETFL, 0);
// flags &= ~O_NONBLOCK;
// fcntl(new_sock, F_SETFL, flags);
// read(new_sock, &recv_buf[0], 1); // Linux
recv(new_sock, &recv_buf[0], 1, 0); // Windows
// printf("recv_buf[0] ->XX%cXX\n",recv_buf[0] );
// printf("recv_buf[2] ->XX%cXX\n",recv_buf[2] );
// printf("recv_buf[5] ->XX%cXX\n",recv_buf[5] );
while (recv_buf[0] == '1') {
ret = 0;
do {
// ret += write(new_sock, clnt_buf, SIZE - ret); // Linux
ret += send(new_sock, clnt_buf, SIZE - ret, 0); // Windows
printf("write %zu bytes\n", ret);
} while (ret != SIZE);
// read(new_sock, &recv_buf, 1); // Linux
recv(new_sock, &recv_buf[0], 1, 0); // Windows
}
printf("Client ask me to close\n");
close(new_sock);
printf("Wait for another connection\n");
start_accept();
}
(6)返回,等待另一个连接请求;
(7)关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
{ close(new_sock);
free(clnt_buf);
clean_up(orig_sock, NAME);
return 0;
}
void clean_up(int sd, const char *the_file) {
close(sd);
unlink(the_file);
}
2、Client端
- 加载套接字库,创建套接字(WSAStartup()/socket);
- 向服务器发出连接请求(connect());
- 和服务器进行通信(send()/recv());
- 关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())
#include <winsock2.h>
typedef int socklen_t;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
const char *NAME = "./my_sock";
#define MSG "Hello world!"
#define MAX 1024
const int PORT = 9535;
int orig_sock;
//int main()
int socket_init(void) {
struct sockaddr_in serv_adr;
// struct hostent *host;
// host = gethostbyname("localhost");
if ((orig_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("generate error");
return 1;
}
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr("47.108.239.67");
// memcpy(&serv_adr.sin_addr, host->h_addr, host->h_length);
serv_adr.sin_port = htons(PORT);
if (connect(orig_sock, (struct sockaddr *) &serv_adr,
sizeof(serv_adr)) < 0) {
perror("connect error");
return 2;
}
return 0;
}
//#define SIZE 1048576*8
#define SIZE 1024
int main() {
WSADATA ws;
WSAStartup(MAKEWORD(2, 2), &ws);
// char buf[MAX];
char *buf;
ssize_t ret = 0;
FILE *fp = fopen("a.bin", "wb");;
buf = malloc(SIZE);
socket_init();
buf[0] = '1';
printf("Sending a request\n");
// write(orig_sock, &buf[0], 1); // Linux
send(orig_sock,&buf[0], 1, 0); // Windows
printf("Waiting for server...\n");
do {
// ret += read(orig_sock, buf + ret, SIZE - ret); // Linux
ret += recv(orig_sock, buf + ret, SIZE - ret, 0); // Windows
} while (ret != SIZE);
printf("read total: %zu bytes\n", ret);
fwrite(buf, SIZE, 1, fp);
buf[0] = '0';
printf("Sending end request, bye~\n");
// write(orig_sock, &buf[0], 1); // Linux
send(orig_sock, &buf[0], 1,0);
printf("\n");
close(orig_sock);
free(buf);
fclose(fp);
getchar();
return 0;
}