linux创建线程执行socket网络编程

一、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;
}
 

注意我直接将服务器地址与端口号写入配置中,地址需要进行改动。这样就可以完成创建线程实现连接,服务端代码类似就不多说了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值