一、socket网络编程
实现一个简单的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.h> //string
#include <unistd.h> //read(),write(),close()
#include <arpa/inet.h> //网络编程库,htonl(),htons()
#include <sys/socket.h> //网络编程库,socket(),bind(),listen(),sockaddr_in等
#include <iostream> //输入输出
#include <stdlib.h> //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 : "<<argv[0]<<" <port>"<<endl;
exit(1);
}
//1.1. 调用socket()创建套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0); //文件描述符
if (serv_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 = htonl(INADDR_ANY); //host类型转成long int
serv_addr.sin_port = htons(atoi(argv[1]));
//host类型转成short int,atoi是字符串转成整型数
//1.2. 调用bind()函数分配IP地址和端口号
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
cout << "bind() error" << endl;
//1.3. 调用listen()函数将套接字转换为可接收连接状态
if (listen(serv_sock, 5) == -1)
cout << "listen() error" << endl;
clnt_addr_size = sizeof(clnt_addr);
//1.4. 调用accept()函数受理连接请求
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1)
cout << "accept() error" << endl;
//1.5. 调用write()/read()函数进行数据传输
write(clnt_sock,message.c_str(),message.length());
//不要写sizeof(message),这是一个定值
close(clnt_sock);
close(serv_sock);
return 0;
}
注意:原博客中write()的第三个参数写的是sizeof(message),我在linux服务器上运行为定值8,并不会把“Hello World!”完整传输,与int a=999,sizeof(a)=4原理一样,与a的值无关只跟a的类型有关,应该用message.length()。举个例子:
string message = "Hello World!";
cout<<message<<endl;
cout<<"sizeof="<<sizeof(message)<<" length="<<message.length()<<endl;
//输出结果如下:
Hello World!
sizeof=8 length=12
客户端:
#include <string.h> //string
#include <unistd.h> //read(),write(),close()
#include <arpa/inet.h> //网络编程库,htonl(),htons()
#include <sys/socket.h> //网络编程库,socket(),bind(),listen(),sockaddr_in等
#include <iostream> //输入输出
#include <stdlib.h> //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)
- 头文件<sys/socket.h>
- 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)
- 头文件<sys/types.h>,<sys/socket.h>
- 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;
/*存储IP地址,内部结构是union{unsigned char/short/long},即有三种类型表示IP地址*/
//其中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)
- 头文件<sys/socket.h>
- 只适用于支持连接的套接口,如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)
- 头文件<unistd.h>
- 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)
- 头文件<sys/socket.h>
- sockfd:连接的套接字
- serv_addr:连接的主机地址与端口号,一般是sockaddr_in设置参数再转换成sockaddr
- addrlen:第二个参数的长度,直接sizeof即可
- 成功返回0,失败为-1
以上参考:1.Socket_Linux(Linux下C++ socket)_FW范伟的博客-CSDN博客_c++ linux socket
进行了一些小改动后可执行
二、线程部分
注意linux线程头文件<pthread.h>在编译时需要添加 -pthread,如:g++ 1.cpp -o 1.exe -pthread
先来看看小案例,流程如下:
(1)pthread_create(),创建线程执行相应的函数
(2)pthread_join(),主线程阻塞等待相应的线程结束,否则主线程过快结束会关闭尚未执行的子线程。
#include <iostream>
#include <pthread.h>
using namespace std;
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void* Thread(void* arg)
{
cout<<"thread"<<endl;
return (char*)"Thread over";
}
int main()
{
int res;
//创建线程变量
pthread_t mythread;
void* thread_result;
//创建 mythread 线程,执行 Thread() 函数
res = pthread_create(&mythread, NULL, Thread, NULL);
if (res != 0) {
cout<<"error"<<endl;
return 0;
}
/*阻塞主线程,直至 mythread 线程执行结束,用 thread_result*/
/*指向接收到的返回值,阻塞状态才消除。*/
res = pthread_join(mythread, &thread_result);
//输出线程执行完毕后返回的数据
cout<< (char*)thread_result<<endl;
cout<< "main over"<<endl;
return 0;
}
相关函数:
(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.h> //string
#include <unistd.h> //read(),write(),close()
#include <arpa/inet.h> //网络编程库,htonl(),htons()
#include <sys/socket.h> //网络编程库,socket(),bind(),listen(),sockaddr_in等
#include <iostream> //输入输出
#include <stdlib.h> //exit(),atoi()
#include <pthread.h>
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"<<endl;
return 0;
}
/*阻塞主线程,直至 mythread 线程执行结束,用 thread_result*/
/*指向接收到的返回值,阻塞状态才消除。*/
res = pthread_join(mythread, &thread_result);
//输出线程执行完毕后返回的数据
cout<< (char*)thread_result<<endl;
cout<< "main over"<<endl;
return 0;
}
注意我直接将服务器地址与端口号写入配置中,地址需要进行改动。这样就可以完成创建线程实现连接,服务端代码类似就不多说了。