tcp
- tcp 设计的初衷是 可靠的数据传输
- 数据包发送后 tcp会确定数据是否正确被接收
- 如果 数据包在 中途丢失 那么tcp协议会重新发送丢失的数据包
- 这一切操作对程序来说是透明的
- 一旦tcp连接成功 那么都可以相互 收发信息
- 我们一般将 发起连接的 终端成为 客户端
- 小心
tcp句柄失效
,作为长时间的可靠服务运行时 一定要对tcp句柄进行检查
tcp 服务器/接收端
- 作为服务器 一定要有地址 可以让 客户端直接发送请求(服务器的ip 要在客户端之上)
- 一个tcp服务器可以同时与多个客户端通信
- 每个与服务器建立链接的客户端都有独立的句柄 可以 收发信息
实例
// server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
/*
windows专属的 初始化api
使用Winsock 之前一定要运行这段代码
*/
void initial_win_socket()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
return;
}
}
//回掉函数的函数指针
typedef void(*p_socket_callback)(SOCKET, sockaddr_in);
//每当建立一个链接就调用这个函数
void socket_callback(SOCKET in_sock, sockaddr_in recvName) {
OutputDebugStringA("我是回掉函数\r\n");
//接收数据
char data_rec[512] = { 0 };
char ip_sender[128] = { 0 };
inet_ntop(AF_INET, &(recvName.sin_addr), ip_sender, 16); //获取客户端的ip
recv(in_sock, data_rec, sizeof(data_rec), 0); //获取接收到的数据
printf("receive from %s:%d : %s\r\n", ip_sender, ntohs(recvName.sin_port), data_rec);
//向客户端发送数据
char str_to_clinet[] = "这是一条服务器回复的数据";
int nRet = send(in_sock, str_to_clinet,sizeof(str_to_clinet),0);
closesocket(in_sock);
}
/*
输入 ip端口 开始绑定
输入 回掉函数,每建立一个socks链接就会调用
最后 别忘记 在回掉函数中关闭 socket接口
closesocket(s);
*/
void bind_port(const char * bind_ip, int bind_port, p_socket_callback fx_cb = NULL)
{
//1 建立SOCKET(套接字)
SOCKET s = socket(AF_INET,
SOCK_STREAM,//数据流形式
IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
return;
}
int nRet;
//2 配置监听端口的结构体
sockaddr_in name;
name.sin_family = AF_INET;
name.sin_port = htons(bind_port);
inet_pton(AF_INET, bind_ip, &(name.sin_addr.S_un.S_addr));//将ip地址转化为 api使用的比特流形式
// 3 将socket 绑定到端口
nRet = bind(s,
(struct sockaddr*)&name,
sizeof(name));
if (nRet == SOCKET_ERROR)
{
return;
}
//4 开始监听
nRet = listen(s, SOMAXCONN/* 设置socket队列最大数量*/);
if (nRet == SOCKET_ERROR)
{
return;
}
//5 等待新的链接建立
sockaddr_in recvName = { 0 };
recvName.sin_family = AF_INET;
int nLength = sizeof(sockaddr_in);
while (1) {
SOCKET sClient = accept(s, (sockaddr*)&recvName, &nLength);//等待链接
if (sClient == INVALID_SOCKET) {
closesocket(sClient);
continue;
}
//如果 传入了 回掉函数就调用回掉函数
if (fx_cb != NULL) {
fx_cb(sClient, recvName);
}
}
closesocket(s);
}
int main()
{
initial_win_socket();
bind_port("127.0.0.1", 10086, socket_callback);
}
tcp 客户端/发送端
实例
// clinet.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
/*
windows专属的 初始化api
使用Winsock 之前一定要运行这段代码
*/
void initial_win_socket()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if(err != 0)
{
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
return;
}
}
/*
向指定 服务器:端口 建立tcp链接
返回 建立的 SOCKET 链接
最后 别忘记 关闭 socket接口
*/
SOCKET connect_to_server(const char * dst_ip, int port)
{
//1 建立SOCKET(套接字)
SOCKET sClient = socket(AF_INET,
SOCK_STREAM,//数据流, 流式
IPPROTO_TCP);
//2 配置 目标服务器的信息
sockaddr_in name;
name.sin_family = AF_INET;
name.sin_port = htons(port);
inet_pton(AF_INET, dst_ip, &(name.sin_addr.S_un.S_addr));//将ip地址转化为 api使用的比特流形式
int nLength = sizeof(sockaddr_in);
//3 建立链接
int nRet = connect(sClient, (sockaddr*)&name, nLength);
if(nRet == SOCKET_ERROR)
{
closesocket(sClient);
return NULL;
}
//4 返回socket句柄
return sClient;
//closesocket(s);
}
int main()
{
initial_win_socket();
SOCKET my_socket = connect_to_server("127.0.0.1", 10086);
//向服务器发送数据
char str_to_server[] = "这是一条客户端发送的数据";
int nRet = send(my_socket, str_to_server, sizeof(str_to_server), 0);
//接收服务器的数据
char data_rec[512] = { 0 };
recv(my_socket, data_rec, sizeof(data_rec), 0);//获取接收到的数据
printf("receive from server: %s\r\n", data_rec);
system("pause");
}