编程小白:利用 Socket API 实现网上点对点通信(windows平台,C语言)

18 篇文章 3 订阅
1 篇文章 0 订阅

client端:(基于TCP)
需要做的事情:

  • 创建socket插口,该插口OS自动分配port。不用init
  • 直接和服务器端的socket进行connect,需要服务器端的ip+port
  • 准备好要发送给服务器端的信息(本实验用的是字符串)send函数
  • 服务器响应回来,用recv函数接受信息。放入某个容器(数组, 甚至是文件)
  • 关闭插口,终止和server端的通信。
#include <WINSOCK2.H>  
#include <STDIO.H>  

#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma  comment(lib,"ws2_32.lib")  //表示链接Ws2_32.lib这个库。


int main(int argc, char* argv[])
{
    WORD sockVersion = MAKEWORD(2, 2);
    WSADATA data;
    if (WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }//上面这几句必不可少,否则无法创建socket插口,即socket()会报错。

    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //客户端的插口
    if (sclient == INVALID_SOCKET)
    {
        printf("invalid socket !");
        return 0;
    }

    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(SERVER_PORT);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    //这就是要准备好server端的信息,便于插口去连接。
    if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }
   const  char* sendData = "hi,I am client!\n";//要发送的信息
    send(sclient, sendData, strlen(sendData), 0);

    char recData[255];
    int ret = recv(sclient, recData, 255, 0);//返回的是实际copy字节数
    if (ret > 0)
    {
        printf(recData);
    }
    closesocket(sclient);
    WSACleanup();//终止对套接字库的使用。
    return 0;
}

server端(基于TCP)

  • server端需要选创建一个socket
  • 然后把server端需要提供服务的那个进程和这个插口绑定一起。
  • listen监听,阻塞到有connect到来
  • accept接收连接
  • 进行recv和send操作,交互数据
  • 循环上述过程,持续监听,可以建立新的连接,然后重复

常用的 Socket 类型有两种:流
式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是
一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket
是一种无连接的 Socket,对应于无连接的 UDP 服务应用。

#include<stdio.h>
#include <winsock2.h>

#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#define BACKLOG 5
#pragma comment(lib, "Ws2_32.lib")
int main()
{

	WORD version(0);
	WSADATA wsadata;
	int socket_return(0);
	version = MAKEWORD(2, 0);
	socket_return = WSAStartup(version, &wsadata);//开始对套接字库的使用。
	if (socket_return != 0)
	{
		return 0;
	}//这些不能少,如果少的话,就无法正确创建socket。



	int ser_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);/*创建连接的SOCKET */

	
	struct sockaddr_in ser_addr; /* server地址信息 */
	struct sockaddr_in cli_addr; /* 客户端地址信息 */
	char msg[MAX_MSG_SIZE];/* 缓冲区,存放从客户端来的请求*/
	memset(msg, 0, MAX_MSG_SIZE);//清空
	
	if (ser_sockfd < 0)
	{/*创建失败 */
		fprintf(stderr, "socker Error:%s\n", strerror(errno));
		exit(1);
	}
	/* 初始化服务器地址*/
	int addrlen = sizeof(struct sockaddr_in);
	memset(&ser_addr, 0, addrlen);
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是本机的所有ip
//如果要手动指定ip可以这么使用
// my_addr.sin_addr.s_addr   =   inet_addr("xxx.xxx.xxx.xxx"); inet_addr用于把32位的ip地址转为unsigned long  

	ser_addr.sin_port = htons(SERVER_PORT);//为这个server进程指定端口号
	if (bind(ser_sockfd, (struct sockaddr*)&ser_addr, sizeof(struct sockaddr_in))
		< 0)
	{ /*绑定失败 */
		fprintf(stderr, "Bind Error:%s\n", strerror(errno));
		exit(1);
	}
	/*侦听客户端请求*/
	if (listen(ser_sockfd, BACKLOG) < 0)//BACKLOG的作用就是设置accpet接收请求队列的大小
	{
		fprintf(stderr, "Listen Error:%s\n", strerror(errno));
		closesocket(ser_sockfd);
		exit(1);
	}

	while (1)
	{/* 等待接收客户连接请求*/
	
		int cli_sockfd = accept(ser_sockfd, (struct sockaddr*)&cli_addr,
			&addrlen);//传入绑定好的进程插口,和客户端地址(ip+port,给出的是cli_addr,以及它的长度)实现通信
	//	printf("%d",cli_sockfd);
		if (cli_sockfd <= 0)
		{
			fprintf(stderr, "Accept Error:%s\n", strerror(errno));
		}
		else
		{/*开始服务*/
			
		/* 接受数据*/
			recv(cli_sockfd, msg, MAX_MSG_SIZE, 0);
			printf("received a connection from %s\n", inet_ntoa(cli_addr.sin_addr));
			printf("%s\n", msg);/*在屏幕上打印出来 */
			//strcpy(msg, "hi,I am server!");
		 /*发送的数据*/
			send(cli_sockfd, "hi,I am server!", sizeof(msg), 0);
			closesocket(cli_sockfd);
		}
	}//server是infinity工作,所以是while(1)
	closesocket(ser_sockfd);
}

基于UDP
server端

没有listen’和accept,直接recvfrom和sendto

首先调用 socket 函数创建一个 Socket,然后调用 bind
函数将其与本机地址以及一个本地端口号绑定,接收到一个客户端时,服务器
显示该客户端的 IP 地址,并将字串返回给客户端。


#include <WINSOCK2.H>  
#include <STDIO.H>  

#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma  comment(lib,"ws2_32.lib")  

int main(int argc, char** argv)
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA data;
	if (WSAStartup(sockVersion, &data) != 0)
	{
		return 0;
	}
	int ser_sockfd;
	int len;
	//int addrlen;
	int addrlen;
	char seraddr[100];
	struct sockaddr_in ser_addr;
	/*建立socket*/
	ser_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (ser_sockfd < 0)
	{
		printf("I cannot socket success\n");
		return 1;
	}
	/*填写sockaddr_in 结构*/
	addrlen = sizeof(struct sockaddr_in);
	memset(&ser_addr, 0, addrlen);

	ser_addr.sin_family = AF_INET;
	ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	ser_addr.sin_port = htons(SERVER_PORT);
	/*绑定客户端*/
	if (bind(ser_sockfd, (struct sockaddr*)&ser_addr, addrlen) < 0)
	{
		printf("connect");
		return 1;
	}
	while (1)
	{
	
		memset(&seraddr, 0, sizeof(seraddr));
		len = recvfrom(ser_sockfd, seraddr, sizeof(seraddr), 0, (struct
			sockaddr*)&ser_addr, &addrlen);
		/*显示client端的网络地址*/
		printf("receive from %s\n", inet_ntoa(ser_addr.sin_addr));
		/*显示客户端发来的字串*/
		printf("recevce:%s", seraddr);
		/*将字串返回给client端*/
		sendto(ser_sockfd, seraddr, len, 0, (struct sockaddr*)&ser_addr, addrlen);
	}
}

client端

首先调用 socket 函数创建一个 Socket,填写服务器地
址及端口号,从标准输入设备中取得字符串,将字符串传送给服务器端,并接
收服务器端返回的字符串。最后关闭该 socket。
没有connect,创建好直接sendto和recvfrom来实现发送接收的通信即可

#include <WINSOCK2.H>  
#include <STDIO.H>  

#define MAX_MSG_SIZE 1024
#define SERVER_PORT 49152
#pragma  comment(lib,"ws2_32.lib") 


int GetServerAddr(char* addrname)
{
	printf("please input server addr:");
	scanf("%s", addrname);
	return 1;
}
int main(int argc, char** argv)
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA data;
	if (WSAStartup(sockVersion, &data) != 0)
	{
		return 0;
	}

	int cli_sockfd;
	int len;

	char seraddr[14];
	struct sockaddr_in cli_addr;
	char buffer[256];
	GetServerAddr(seraddr);
	/* 建立socket*/
	cli_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (cli_sockfd < 0)
	{
		printf("I cannot socket success\n");
		return 1;
	}
	/* 填写sockaddr_in*/
	int addrlen = sizeof(struct sockaddr_in);


	memset(&cli_addr, 0, addrlen);


	cli_addr.sin_family = AF_INET;
	cli_addr.sin_addr.s_addr = inet_addr(seraddr);
	//cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	cli_addr.sin_port = htons(SERVER_PORT);

	memset(buffer, 0, sizeof(buffer));


	/* 从标准输入设备取得字符串*/
	scanf("%s", buffer);
	len = strlen(buffer);
	/* 将字符串传送给server端*/
	sendto(cli_sockfd, buffer, len, 0, (struct sockaddr*)&cli_addr, addrlen);
	/* 接收server端返回的字符串*/
	len = recvfrom(cli_sockfd, buffer, sizeof(buffer), 0, (struct
		sockaddr*)&cli_addr, &addrlen);
	//printf("receive from %s\n",inet_ntoa(cli_addr.sin_addr));
	printf("receive: %s", buffer);
	
	closesocket(cli_sockfd);
}

常用的 Socket 类型有两种:流
式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是
一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket
是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
TCP

优点:
1.TCP 提供以认可的方式显式地创建和终止连接。
2.TCP 保证可靠的、顺序的(数据包以发送的顺序接收)以及不会重复
的数据传输。
3.TCP 处理流控制
4.允许数据优先
5.如果数据没有传送到,则 TCP 套接口返回一个出错状态条件。
6.TCP 通过保持连续并将数据块分成更小的分片来处理大数据块。—无
需程序员知道

缺点: TCP 在转移数据时必须创建(并保持)一个连接。这个连接给通
信进程增加了开销,让它比 UDP 速度要慢。

UDP 优缺点:
1.UDP 不要求保持一个连接
2.UDP 没有因接收方认可收到数据包(或者当数据包没有正确抵达而自
动重传)而带来的开销。
3.设计 UDP 的目的是用于短应用和控制消息
4.在一个数据包连接一个数据包的基础上,UDP 要求的网络带宽比 TCP
更小。

对于UDP传输需要指定超时了解客户端断开连接

使用setsockopt函数来实现超时判断。
注意,这个函数是全局作用的,所以放在开头指定这个端口在recv的时候一旦过了指定的time就默认连接断开即可。

int nNetTimeout = 3000; //3000ms超时
	if (SOCKET_ERROR == setsockopt(ser_sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&nNetTimeout, sizeof(int)))
	{
		printf("Set Ser_RecTIMEO error !\r\n");
	}

第一个参数指定了作用的socket是哪一个。SOL_SOCKET就是通用的socket选项。SO_RCVTIMEO指定了是接收侦听的时候判断超时。nNetTimeout设定了要判断超时的时间有多久。最后一个是对这个时间(int)的大小传入。
这样在后续recv接收返回值的时候,就可以对ret和0比较。小等于0,就是断开连接。
代码如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值