linux下socket编程的一些学习经历(一)

首次接触socket编程,通过在linux下实现一个简单聊天程序的客户端和服务端,加深对socket和linux的一些了解.
本篇先实现服务端的一些功能.

首先创建socket,在服务端socket需要绑定本地端口,对其进行相应初始化
///定义sockfd
///流式Socket(SOCK_STREAM)针对于面向连接的TCP
///数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP
int server_create = socket(AF_INET,SOCK_STREAM, 0);
if(server_create == -1)
{
	perror("socket create false");
	exit(1);
}
///定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVERPORT);
server_sockaddr.sin_addr.s_addr = inet_addr(SERVERIP);

socket结构体的相应细节许多人之前也都已经提到过,这里不再赘述.

socket在创建完成后,需要进行绑定

///bind,成功返回0,出错返回-1
///struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,
///二者长度一样,都是16个字节.二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr,
///一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中.
if(bind(server_create,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
	perror("bind");
	exit(1);
}
接下来将socket设置为监听

///sten,成功返回0,出错返回-1
///socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,
///等待客户的连接请求.
if(listen(server_create,QUEUE) == -1)
{
	perror("listen");
	exit(1);
}
至此socket就可以监听其他socket所发送的connect请求了,但是由于服务端需要同时处理多个用户的请求,所以此处使用多线程进行相应处理.

思路大概为本socket每监听到一个请求,便创建一个新的线程来处理.

使用一个while来实现

mi = 0;
while(1)
{
	struct conMySocket my_consok;

	socklen_t length = sizeof(my_consok.clientSocket);

	///成功返回非负描述字,出错返回-1
	///accept在此处为阻塞函数,提取出所监听套接字的等待连接队列中第一个连接请求
	my_consok.con = accept(server_create, (struct sockaddr*)&(my_consok.clientSocket), &length);

	if(my_consok.con < 0)
	{
		perror("connect");
		exit(1);
	}
	if(pthread_create(&thrid[mi], NULL, (void *)client_mythrdeal, &my_consok))
	{
		printf ("Create pthread error!\n");
		exit(1);
	}
	mi++;
}
服务端的主要作用是为接入的用户提供其他用户的IP及端口号等相应信息,用户同用户的交流由客户端实现.

至此服务端主线程已经实现得差不多了,接下来是监听后创建的单独处理的线程所要实现的功能.

int client_mythrdeal(void* pmy_consok)
{
	char *ip = NULL;
	struct conMySocket myConSok = *(struct conMySocket*)pmy_consok;
	struct user_info nowUserInfo;
	memset(&nowUserInfo, 0, sizeof(struct user_info));
	//inet_ntoa要求的参数只需要到sin_addr,而sin_addr下的s_addr不需要
	strcpy(nowUserInfo.user_ipadrs, inet_ntoa(myConSok.clientSocket.sin_addr));
	sprintf(nowUserInfo.user_port, "%d", ntohs(myConSok.clientSocket.sin_port));

	ip = inet_ntoa(myConSok.clientSocket.sin_addr);

	printf("IP:%s:%d\n", ip, myConSok.clientSocket.sin_port);
	int nowId = 0;
	while(1)
	{
		char buffer[BUFFER_SIZE];
		memset(buffer, 0, BUFFER_SIZE * sizeof(char));
		recv(myConSok.con, buffer, BUFFER_SIZE * sizeof(char), 0);
		printf("%s\n", buffer);
		//若为测试连接标志,发送确定标志返回
		if(strcmp(buffer, TESTSIGN) == 0)
		{
			send(myConSok.con, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
			continue;
		}
		//若为发送端口标志,发送确定标志并准备接收端口号并对nowUserInfo.user_serverport赋值
		if(strcmp(buffer, SENDPORTSIGN) == 0)
		{
			send(myConSok.con, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
			memset(buffer, 0, BUFFER_SIZE * sizeof(char));
			recv(myConSok.con, buffer, BUFFER_SIZE * sizeof(char), 0);
			strcpy(nowUserInfo.user_serverport, buffer);
		}
		//若为登陆核对标志,则调用账户密码核对函数
		if(strcmp(buffer, CHECKSIGN) == 0)
		{
			nowId = checkAccountAndPassword(myConSok.con, nowUserInfo.user_ipadrs, nowUserInfo.user_port, nowUserInfo.user_serverport);
			continue;
		}
		//若为请求在线用户信息标志,则调用在线用户信息反馈函数
		if(strcmp(buffer, ONLINEUSERINFOREQUEST) == 0)
		{
			onlineUserInfoRet(myConSok.con, nowId);
			continue;
		}
		//若为请求其他用户IP和绑定的端口号,则调用tagUserIPAndPort函数
		if(strcmp(buffer, OTHERUSERSOCKETREQUEST) == 0)
		{
			tagUserIPAndPort(myConSok.con);
			continue;
		}
	}
	close(myConSok.con);
	return 0;
}

接下来是相应的功能函数实现

//账户密码核对
int checkAccountAndPassword(int requestSocket, char *user_ipadrs, char *user_port, char *user_serverport)
{
	char userAccount[SHORT_STRING], userPassword[SHORT_STRING];
	memset(userAccount, 0, SHORT_STRING * sizeof(char));
	memset(userPassword, 0, SHORT_STRING * sizeof(char));
	send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
	recv(requestSocket, userAccount, SHORT_STRING * sizeof(char), 0);	
	recv(requestSocket, userPassword, SHORT_STRING * sizeof(char), 0);
	int i = 0;//计数用
	while(userlist[i] != NULL)
	{
		if((strcmp((*userlist[i]).user_account, userAccount) == 0) && (strcmp((*userlist[i]).user_password, userPassword) == 0))
		{
			//若账户密码核对正确,则将用户当前客户端所具信息存储到用户信息表中,并将设为用户设为在线状态
			strcpy((*userlist[i]).user_ipadrs, user_ipadrs);
			strcpy((*userlist[i]).user_port, user_port);
			strcpy((*userlist[i]).user_serverport, user_serverport);
			(*userlist[i]).yn_online = 1;
			send(requestSocket, LOGINSUCCESSRET, sizeof(LOGINSUCCESSRET), 0);
			return i;
		}
		i++;
	}
	send(requestSocket, LOGINFAILURERET, sizeof(LOGINFAILURERET), 0);
	return 0;
}
//在线用户信息反馈
int onlineUserInfoRet(int requestSocket, int selfId)
{
	int i = 0;
	char onlineUserInfoStr[MAXINFOSTRLENGTH];
	memset(onlineUserInfoStr, 0, MAXINFOSTRLENGTH * sizeof(char));
	send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
	while(userlist[i] != NULL)
	{
		if((*userlist[i]).yn_online == 1 && i != selfId)
		{
			char id[10];
			memset(id, 0, 10 * sizeof(char));
			sprintf(id, "%d", (*userlist[i]).id_num);
			strcat(onlineUserInfoStr, "  ");
			strcat(onlineUserInfoStr, id);
		}
		i++;
	}
	if(strlen(onlineUserInfoStr))
		send(requestSocket, onlineUserInfoStr, sizeof(onlineUserInfoStr), 0);
	else
		send(requestSocket, NOBODYONLIEN, sizeof(NOBODYONLIEN), 0);
}

//将其他用户IP和端口号反馈给请求用户
int tagUserIPAndPort(int requestSocket)
{
	int i = 0;
	char tagUserIdStr[SHORT_STRING];
	memset(tagUserIdStr, 0, SHORT_STRING * sizeof(char));
	send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
	recv(requestSocket, tagUserIdStr, SHORT_STRING * sizeof(char), 0);
	while(userlist[i] != NULL)
	{
		if((*userlist[i]).id_num == atoi(tagUserIdStr))
		{
			//发送用户存在标志后,继续发送用户IP和用户所在客户端的服务端绑定的端口号
			send(requestSocket, USEREXIST, sizeof(USEREXIST), 0);
			send(requestSocket, (*userlist[i]).user_ipadrs, SHORT_STRING * sizeof(char), 0);
			send(requestSocket, (*userlist[i]).user_serverport, SHORT_STRING * sizeof(char), 0);
			return 0;
		}
		i++;
	}
	send(requestSocket, USERNOEXIST, sizeof(USERNOEXIST), 0);
	return 1;
}

服务端的主要代码至此已经编写的差不多了,接下来总结一下需要注意的细节吧.

首先是pthread的线程函数的参数传递,若要传递多个参数,可先创建一个结构体,然后将结构体的指针作为线程函数的参数传递进去.

另外在gcc下貌似是没有itoa函数的,可以用sprintf来代替.

由于用到了pthread,在编译的时候需加上-pthread

服务端的总结大概就到这吧,接下来是客户端的实现.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值