实现一个简单的socket连接,来尝试一下linux网络编程吧。
首先分为服务端和客户端两个部分:
1、server服务端流程:
(1)socket(),创建套接字,返回int类型,可以看作连接通道的id,linux下一切皆文件,所以此处就是文件描述符。
(2)bind(),给此文件描述符绑定一个本地地址。
(3)listen(),监听连接请求
(4)accept(),接收连接请求,建立连接
(5)read()/ write(),数据读取/传输,此处为服务器发送也就是write
(6)close(),断开连接
2、client客户端流程:
(1)socket(),创建套接字,同上
(2)connect(),请求连接
(3)read () / write () ,数据读取/传输,此处为客户端接收也就是read
(4)close(),断开连接
先来看看服务端整体代码吧:
#include//string #include //read(),write(),close() #include //网络编程库,htonl(),htons() #include //网络编程库,socket(),bind(),listen(),sockaddr_in等 #include //输入输出 #include //exit(),atoi() using namespace std; int main(int argc, char* argv[]) { int serv_sock; //服务器套接字描述符 int clnt_sock; //客户端套接字描述符,同上,以下类似的就不重复了 struct sockaddr_in serv_addr; //地址信息结构体 struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; //accept()的第三个参数是socklen_t类型,表示长度 string message = "Hello World!"; if (argc != 2) { //如果命令行不是输入两个参数 cout<<"Usage : "<"< 注意:原博客中write()的第三个参数写的是sizeof(message),我在linux服务器上运行为定值8,并不会把“Hello World!”完整传输,与int a=999,sizeof(a)=4原理一样,与a的值无关只跟a的类型有关,应该用message.length()。举个例子:
string message = "Hello World!"; cout<#include //string #include //read(),write(),close() #include //网络编程库,htonl(),htons() #include //网络编程库,socket(),bind(),listen(),sockaddr_in等 #include //输入输出 #include //exit(),atoi() using namespace std; int main(int argc, char* argv[]) { int sock; //套接字描述符 struct sockaddr_in serv_addr; //地址信息结构体 char message[30]; //发送的内容 int str_len = 0; int idx = 0, read_len = 0; if (argc != 3) { //命令行输入不为三个参数 cout << "Usage : %s IP port" << argv[0] << endl; exit(1); } //2.1. 调用socket()创建套接字 sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) cout << "socket() error" << endl; //地址初始化 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); //2.2.调用connect()函数向服务器发送连接请求 if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) cout << "connect() error!" << endl; //2.3. 调用write()/read()函数进行数据传输 while (read_len = read(sock, &message[idx++], 1)) { if (read_len == -1) cout << "read() error!" << endl; str_len += read_len; } cout << "Message from server: " << message << endl; cout << "Function read call count: " << str_len << endl; close(sock); return 0; } 这个例子为服务器接收到客户端发送的连接请求后,给客户端发送“Hello World!”后服务端关闭,客户端接收信息并显示后关闭。
//server服务端终端,需要输入端口号,默认80是开启的,也可以通过更改配置打开其它端口 g++ server.cpp -o server.exe ./server.exe 80 //client客户端终端,需要输入地址和端口号 g++ client.cpp -o client.exe ./client.exe xxx.xxx.xxx.xxx 80
介绍一下相关函数:
(1)int socket( int af, int type, int protocol)
- 头文件
- af:指的是地址描述,常用:AF_INET,PF_INET,理论上指定协议用PF(protocol family),设置地址用AF(address family),但两者值相同,混用也无关紧要,一个PF只有一个AF,可以当作TCP/IP的设计者预留的吧,目前无差别。
- type:指定socket类型,常用SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
- protocol:指定协议,不指定协议直接写0即可,常用IPPROTO_TCP、IPPROTO_UDP,即TCP和UDP
- 成功返回套接字描述符,失败为-1
(2)int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen)
- 头文件
, - sockfd:要绑定的套接字描述符
- my_addr是指向sockaddr结构体的指针,一般是sockaddr_in设置参数再转换成sockaddr
- addrlen:第三个参数是第二个参数的长度,直接用sizeof直接获得即可
- 成功返回0,失败为-1
来看看sockaddr_in结构体:
struct sockaddr_in { short int sin_family; //通常是AF_INET unsigned short int sin_port; //端口号 struct in_addr sin_addr; //其中sin_addr.s_addr是unsigned long类型 unsigned char sin_zero[8]; //为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 };简化代码:
int serv_sock; //服务器文件描述符 struct sockaddr_in serv_addr; //地址信息结构体 //1.1. 调用socket()创建套接字 serv_sock = socket(PF_INET, SOCK_STREAM, 0); //文件描述符 memset(&serv_addr, 0, sizeof(serv_addr));//地址初始化 serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //host类型转成long int serv_addr.sin_port = htons(atoi(argv[1])); //host类型转成short int,atoi是字符串转成整型数 //1.2. 调用bind()函数分配IP地址和端口号 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));(3)int listen( int sockfd, int backlog)
- 头文件
- 只适用于支持连接的套接口,如TCP
- sockfd:套接字描述符
- backlog:等待连接队列的最大长度,此处为5
- 成功返回0,失败为-1
(4)int accept( int sockfd, struct sockaddr *addr, socklen_t *addrlen)
- sockfd:套接字描述符
- addr:(可选)指向一缓冲区指针,为接收连接实体的地址
- addrlen:(可选)指针,指向存有addr地址长度的整型数,由于是指针所以不能直接sizeof
- 成功返回接收的套接字描述符,失败为-1
(5)ssize_t read ( int fd, void *buf, size_t count)
size_t write (int fd,const void * buf,size_t count)
- 头文件
- fd:要读取/写入的套接字描述符
- buf:read()为在buf所指的内存中保存从fd读取的内容,write()为将buf中的内容写入到fd
- count:读取/写入的大小,若未给定或为负数则读取全部
- 成功返回读取/写入的字节数,ssize_t在32位上是int,64位是long,与size_t基本相同,有符号而已,size_t为unsigned int/long。失败则返回-1
(6)int connect(int sockfd, struct sockaddr * serv_addr, int addrlen)
- 头文件
- sockfd:连接的套接字
- serv_addr:连接的主机地址与端口号,一般是sockaddr_in设置参数再转换成sockaddr
- addrlen:第二个参数的长度,直接sizeof即可
- 成功返回0,失败为-1
以上参考:1.Socket_Linux(Linux下C++ socket)_FW范伟的博客-CSDN博客_c++ linux socket
进行了一些小改动后可执行
二、线程部分注意linux线程头文件
在编译时需要添加 -pthread,如:g++ 1.cpp -o 1.exe -pthread 先来看看小案例,流程如下:
(1)pthread(),创建线程执行相应的函数
(2)pthread_join(),主线程阻塞等待相应的线程结束,否则主线程过快结束会关闭尚未执行的子线程。
#include#include using namespace std; //定义线程要执行的函数,arg 为接收线程传递过来的数据 void* Thread(void* arg) { cout<<"thread"< 相关函数:
(1)int pthread_create(pthread_t *tidp , const pthread_attr_t *attr , void *(*start_rtn)(void*) , void *arg)
- *tidp:指向线程标识符的指针
- *attr:设置线程属性,继承性,作用域等属性,这里不作考虑,此处为NULL
- *(*start_rtn)(void*):函数指针,线程运行函数的起始地址
- *arg:函数传参,此处为NULL,如需要传输多个需要放入结构体中
(2)int pthread_join(pthread_t thread, void ** retval)
三、综合应用
- thread:所要等待的线程
- **retval:自定义的指针,用来存储被等待线程的返回值
说是综合应用,其实只是把连接这块放到线程中,主线程阻塞等待而已。通常情况下子线程设置为结束自动释放,主线程循环,此处只做简单示例,客户端示例:
#include//string #include //read(),write(),close() #include //网络编程库,htonl(),htons() #include //网络编程库,socket(),bind(),listen(),sockaddr_in等 #include //输入输出 #include //exit(),atoi() #include using namespace std; void* Thread(void* arg) { int sock; //套接字描述符 struct sockaddr_in serv_addr; //地址信息结构体 char message[30]; //发送的内容 int str_len = 0; int idx = 0, read_len = 0; //2.1. 调用socket()创建套接字 sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) cout << "socket() error" << endl; //地址初始化 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");//argv[1] //注意此处需要将xxx改成地址!!!!!!!!!!!!!!!!!!!!!!!!!! serv_addr.sin_port = htons(atoi("80"));//argv[2] //2.2.调用connect()函数向服务器发送连接请求 if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) cout << "connect() error!" << endl; //2.3. 调用write()/read()函数进行数据传输 while (read_len = read(sock, &message[idx++], 1)) { if (read_len == -1) cout << "read() error!" << endl; str_len += read_len; } cout << "Message from server: " << message << endl; cout << "Function read call count: " << str_len << endl; close(sock); } int main(int argc, char* argv[]) { int res; //创建线程变量 pthread_t mythread; void* thread_result; //创建 mythread 线程,执行 Thread() 函数 res = pthread_create(&mythread, NULL, Thread, NULL); if (res != 0) { cout<<"error"< 注意我直接将服务器地址与端口号写入配置中,地址需要进行改动。这样就可以完成创建线程实现连接,服务端代码类似就不多说了。



