server的编程
分为6个部分
1.socket
2.bind
3.listen
4.accept
5.read
6.write
socket
socket返回的值是一个文件描述符,SOCKET类型本身也是定义为int的
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain里面填这些
AF_UNIX, AF_LOCAL
Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk ddp(7)
AF_PACKET Low level packet interface packet(7)
在socket创建后,我们还需要为struct sockaddr这个结构体赋值
在头文件下查找这个结构体包含的内容
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Internet address. */
struct in_addr {
__be32 s_addr;
};
在填写结构体内容时,注意转换类型 用到htons,atoi等。 意思是a 码转int型。
h to ns.
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
printf("create socket successed \n");
type里面写这些
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based
byte streams. An out-of-band data transmission mechanism
may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages
of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based
data transmission path for datagrams of fixed maximum
length; a consumer is required to read an entire packet
with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guaran‐
tee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see
packet(7).
bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
实战编程:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
int s_fd;
int c_fd;
int nread;
char readbuf[128];
char msg[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket 创建socket
s_fd = socket( AF_INET ,SOCK_STREAM,0);
//sever send message
if(s_fd == -1){
perror("error");
exit(-1);
}
// int socket(int domain, int type, int protocol);
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
printf("create socket successed \n");
//2.bind 绑定
//int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in ));
printf("bind successed \n");
//3.listen
// int listen(int sockfd, int backlog);
listen(s_fd,10);
printf("listen ok\n");
//4.accept
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// client accept message
int clen = sizeof(struct sockaddr_in);
//5.read
c_fd = accept(s_fd,(struct sockaddr *)&s_addr,&clen);
if(c_fd == -1){
perror("accept");
}
printf("start while\n");
while(1){
printf("get connect %s\n",inet_ntoa(c_addr.sin_addr));
printf("start fork\n");
//调用线程实现双方同步聊天,用read write交流
if(fork()==0){
while(1){
//6.write
memset(msg,0,sizeof(msg));//每次初始化缓存,防止内容重复
printf("please input:");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
while(1){
memset(readbuf,0,sizeof(readbuf)); //
nread = read(c_fd,readbuf,128);
if(nread == -1){
perror("read");
}else {
printf("read successed :size :%d text :%s\n",nread,readbuf);
}
}
}
return 0;
}
client部分
1.socket
2.connect
3.read
4.write
connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
//const struct sockaddr *addr 这用 (struct sockaddr *)强制转换
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
int c_fd;
int nread;
char readbuf[128];
char msg[128]={0};
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
c_fd = socket( AF_INET ,SOCK_STREAM,0);
//sever send message
if(c_fd == -1){
perror("socket");
exit(-1);
}
// int socket(int domain, int type, int protocol);
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&c_addr.sin_addr);
printf("create socket successed \n");
// int connect(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))== -1)//客户端 直接用connect
{
perror("connect");
exit(-1);
}
while(1){
if(fork()==0){
while(1){
memset(msg,0,sizeof(msg));
printf("input:");
gets(msg);
write(c_fd,msg,strlen(msg));
}
}
//5.readd
while(1){
memset(readbuf,0,sizeof(readbuf));
nread = read(c_fd,readbuf,128);
if(nread == -1){
perror("read");
}else {
printf("read successed :size :%d text :%s\n",nread,readbuf);
}
}
}
//6.write
printf("connect successed \n");
return 0;
}
功能进阶:实现多方聊天
建立一个mark计数符, 进行每三秒中向客户端发送一个id来表示连接
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
int mark =0 ;
int s_fd;
int c_fd;
int nread;
char readbuf[128];
char msg[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
s_fd = socket( AF_INET ,SOCK_STREAM,0);
//sever send message
if(s_fd == -1){
perror("error");
exit(-1);
}
// int socket(int domain, int type, int protocol);
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(atoi(argv[2]));
inet_aton(argv[1],&s_addr.sin_addr);
printf("create socket successed \n");
//2.bind
//int bind(int sockfd, const struct sockaddr *addr,
// socklen_t addrlen);
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in ));
printf("bind successed \n");
//3.listen
// int listen(int sockfd, int backlog);
listen(s_fd,10);
printf("listen ok\n");
//4.accept
// int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// client accept message
int clen = sizeof(struct sockaddr_in);
//5.read
while(1){
c_fd = accept(s_fd,(struct sockaddr *)&s_addr,&clen);
if(c_fd == -1){
perror("accept");
}
mark++;
printf("get connect %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0){
if(fork()==0){
while(1){
//6.write
sprintf(msg,"welcome NO.%d client\n",mark);
write(c_fd,msg,strlen(msg));
sleep(3);
}
}
while(1){
memset(readbuf,0,sizeof(readbuf));
nread = read(c_fd,readbuf,128);
if(nread == -1){
perror("read");
}else {
printf("read successed :size :%d text :%s\n",nread,readbuf);
}
}
}
}
return 0;
}
结合gtk 图形界面
client 客户端
#include <gtk/gtk.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define OURPORT 4321
gint sd;
struct sockaddr_in s_in;
gchar username[64];
gchar buf[1024];
gchar get_buf[1048];
gboolean isconnected = FALSE;
static GtkWidget *text;
static GtkTextBuffer *buffer; //缓存
static GtkWidget *message_entry;//消息
static GtkWidget *name_entry;//用户名
static GtkWidget *login_button;//登入按钮
void get_message()
{
GtkTextIter iter;
gchar get_buf[1024];
gchar buf[1024];
gint num = -1;
while(num = recv(sd, buf, 1024,0)) { //接收 s_fd 的消息到缓存
if (num == -1 || num == 0) break;//未读取到消息 break
sprintf(get_buf, "%s", buf);
gdk_threads_enter();
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, get_buf, -1);
gdk_threads_leave();
}
}
gboolean do_connect_run()
{
struct hostent *host;
GtkTextIter iter;
gint slen;
sd = socket(AF_INET, SOCK_STREAM, 0); //socket 编程的文件符 int s_fd
if (sd < 0) {
// gtk_text_buffer_get_start_iter(Send_buffer,&start);/*得到当前文本区域的起始位置*/
// gtk_text_buffer_get_end_iter(Send_buffer,&end);/*得到当前文本区域的结束位置*/
//应该类似读写文件时的光标操作?
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, "打开套接字时出错!\n", -1);
///*插入文本到缓冲区*/
return FALSE;
}
//为server的结构体赋值, 具体结构体参数可以去头文件里查询
s_in.sin_family = AF_INET;
//地址协议 man手册里面有具体介绍
host=gethostbyname("127.0.0.1");
//本地IP地址,127.0.0.1为默认(类似于中文的“我自己”),192.168.x.x为真实地址
//需要时可 修改ip地址 为了运行
s_in.sin_addr=*((struct in_addr *)host->h_addr);
//存放地址信息
s_in.sin_port = htons(OURPORT);
//端口 socket编程要两个参数, 一个ip 一个端口 端口要大一点 不然会有影响
slen = sizeof(s_in);//结构体大小
//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// const struct sockaddr *addr 这里需要强转struct类型
//函数返回值 成功返回0,失败返回-1,设置errno
if (connect(sd, (struct sockaddr*)&s_in, slen) < 0) {
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, "连接服务器时出错!\n", -1);
//同上 插入一段文字到缓冲区
return FALSE;
}
else {
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, username, -1);
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, "成功与服务器连接...\n", -1);
//成功连接
//将用户名写入 s_fd 发给服务器
write(sd, username, 64);
isconnected = TRUE;
return TRUE;//检测 connect 返回bool 这个函数用来判定是否正确连接服务器
}
}
void on_destroy(GtkWidget *widget, GdkEvent *event, gpointer data)
{
sprintf(username, "guest");
if(do_connect_run() == TRUE) {
gtk_widget_set_sensitive(login_button, FALSE);
g_thread_create((GThreadFunc)get_message, NULL, FALSE, NULL);
}
gtk_widget_destroy(widget);
}
void on_button_clicked(GtkButton *button, gpointer data)
{
const gchar *name;
name = gtk_entry_get_text(GTK_ENTRY(name_entry));//获取用户名
sprintf(username, "%s", name);//将name 输入到username
if (do_connect_run()) {
gtk_widget_set_sensitive(login_button, FALSE);
g_thread_create((GThreadFunc)get_message, NULL, FALSE, NULL);
}
gtk_widget_destroy(GTK_WIDGET(data));
}
void create_win()
{ //创建 登入 图形界面 为用户名 赋值
GtkWidget *win, *vbox;
GtkWidget *button;
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);//创建新窗口在最上层
g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(on_destroy), NULL);
gtk_window_set_title(GTK_WINDOW(win), "输入用户名");
gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);//设置窗口位置
gtk_container_set_border_width(GTK_CONTAINER(win), 10);//设置容器参数
gtk_window_set_modal(GTK_WINDOW(win), TRUE);
//设置窗口模式
vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(win), vbox);
name_entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(vbox), name_entry, TRUE, TRUE, 5);
//用户名 输入文本框
button = gtk_button_new_from_stock(GTK_STOCK_OK);
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(on_button_clicked), win);
gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 5);
// 按钮 跳转
gtk_widget_show_all(win);
}
void on_send(GtkButton *button, gpointer data)
{
const gchar *message;
if (isconnected == FALSE) return; //没连接 退出
message = gtk_entry_get_text(GTK_ENTRY(message_entry));
if (g_strcmp0(message, "") == 0) return; //消息为空 退出
sprintf(buf, "%s", message);
send(sd, buf, 1024,0);// 缓冲区内容发送到fd
gtk_entry_set_text(GTK_ENTRY(message_entry), "");//消息框 清空!
}
void on_login(GtkWidget *button, gpointer data)
{
create_win();
}
void on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
close(sd);
gtk_main_quit();//关闭 s_fd gtk图形界面退出!
}
int main(int argc, char *argv[])
{
GtkWidget *window;//窗口创建
GtkWidget *vbox, *hbox, *button, *label, *view;
//消息框, 按钮, 标签
if (!g_thread_supported()) {
g_thread_init(NULL);
}
else {
g_print("thread not supported\n");
}//创建线程
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(on_delete_event), NULL);
// GTK+2.0中利用信号/回调函数机制来处理窗口外部传来的事件、消息或信号, 调用函数!!!
gtk_window_set_title(GTK_WINDOW(window), "客户端");//窗口标题命名
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
//设置窗口位置 在中间
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
//为容器在窗口中设置空间
//GtkWidget * gtk_vbox_new (gboolean homogeneous, gint spacing);
//homogeneous是一个布尔值,为TRUE时,强制盒中的构件都占用相同大小的空间,不管每个空间的大小
vbox = gtk_vbox_new(FALSE, 0);//定义垂直容器创建
//void gtk_container_add(GtkContainer *container, GtkWidget *widget);
//container:容纳控件的容器 ,widget:要添加的控件
gtk_container_add(GTK_CONTAINER(window), vbox);//添加容器
hbox = gtk_hbox_new(FALSE, 0);//同上 表示水平容器
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
把 hbox 安排在 vbox 内
label = gtk_label_new("点击登录按钮连接服务器");//标签,提示用户操作
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
//同上 将label安排在hbox内
login_button = gtk_button_new_with_label("登录");//登入按钮
gtk_box_pack_start(GTK_BOX(hbox), login_button, FALSE, FALSE, 5);
//将登入button安排在hbox中
g_signal_connect(G_OBJECT(login_button), "clicked", G_CALLBACK(on_login), NULL);
//g_signal_connect(按钮,"单击",G_CALLBACK(& MainWindow: nButtonClicked),NULL);
// 收到点击按钮信号,触发 login 函数
view = gtk_scrolled_window_new(NULL, NULL);//产生一个有卷轴的窗口
//有些元件預設並沒有捲軸,當視窗或父元件無法顯示其大小時,只會顯示部份區域,
//但不會出現捲軸,如果您希望這類元件可以出現捲軸,則可以使用GtkScrolledWindow
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(view), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
//名称:: gtk_scrolled_window_set_policy 功能: 设置滚动条出现的方式
text = gtk_text_view_new();
gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 5);
gtk_container_add(GTK_CONTAINER(view), text);
//生成一个输入框 放在卷轴窗口内?
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
//名称::gtk_text_view_get_buffer
//功能:获得文本框的缓冲区
//函数原形:GtkTextBuffer* gtk_text_view_get_buffer (GtkTextView *text_view);
//
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
//继续生成一个水平容器放在vbox 垂直容器里面
label = gtk_label_new("输入信息:");
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
//生成一个label标签: 输入信息 用来提示用户
message_entry = gtk_entry_new();
gtk_box_pack_start(GTK_BOX(hbox), message_entry, FALSE, FALSE, 5);
//消息输入窗口
button = gtk_button_new_with_label("发送");
gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 5);
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(on_send), NULL);
//生成发送按钮
gtk_widget_show_all(window);//显示所有容器中的控件
gdk_threads_enter();
gtk_main();
gdk_threads_leave();
// 调用进程更新界面
return TRUE;
}
服务器端代码实战!
#include <glib.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#define OURPORT 4321
#define MAX_USERS 8
struct _client
{
gint sd;
gboolean in_use;
gchar name[64];
gchar buf[1024];
};
typedef struct _client client;
//定义存放 客户端消息的结构体
client user[MAX_USERS];
//客户端 变量声明 限制最大在线数量
void do_service(gpointer id)
{
gint j;
gchar tobuf[1024] = {0};
gint num = -1;
while(num = recv(user[GPOINTER_TO_INT(id)].sd, user[GPOINTER_TO_INT(id)].buf, 1024,0)) {
if (num == -1 || num == 0) break;
sprintf(tobuf, "%s:%s\n", user[GPOINTER_TO_INT(id)].name, user[GPOINTER_TO_INT(id)].buf);
// 把用户 用户名字 用户信息 写到缓存中
for(j = 0; j < MAX_USERS; j++) {
if (user[j].in_use) {
send(user[j].sd, tobuf, 1024,0);
printf("%s", tobuf);
}
}//遍历用户!! 输出 缓存
}
user[GPOINTER_TO_INT(id)].in_use = FALSE;
close(user[GPOINTER_TO_INT(id)].sd);
}
int main(int argc, char *argv[])
{
gint sd, newsd;
struct sockaddr_in *sin;
gint slen;
gint count = 0;
gint flags;
gchar buf[1024];
gchar tobuf[1024];
gint length, i, j;
if (!g_thread_supported()) {
g_thread_init(NULL);
}
else {
g_print("thread not supported\n");
}
sd = socket(AF_INET, SOCK_STREAM, 0);
// 服务器 socket文件s_fd 变量声明
if (sd == -1) {
g_print("create socket error!\n");
return -1;
}
sin = g_new(struct sockaddr_in, 1);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr=inet_addr("127.0.0.1");
sin->sin_port = htons(OURPORT);
//socket服务器结构体赋值 , 具体内容 去头文件中查询
slen = sizeof(struct sockaddr_in);
if (bind(sd, (struct sockaddr*)sin, slen) < 0) {
g_print("bind error!\n");
return -1;
}
//绑定 套字节 将服务器 与 127.0.0.1 和端口 ourport 绑定
//例如 ./server 127.0.0.1 8989
if (listen(sd, 8) < 0) {
g_print("listen error!\n");
return -1;
}
//监听, 定义服务器监听的个数
for (i = 0; i < MAX_USERS; i++) {
user[i].in_use = FALSE;
}
flags = fcntl(sd, F_GETFL);
//fcntl是计算机中的一种函数,通过fcntl可以改变已打开的文件性质。
//fcntl针对描述符提供控制。
//参数fd是被参数cmd操作的描述符。针对cmd的值,fcntl能够接受第三个参数int arg。进行阻塞
fcntl(sd, F_SETFL, flags &~O_NDELAY);
for(;;) {
newsd = accept(sd, (struct sockaddr*)sin, (socklen_t *)&slen);
//accept 函数 服务器接收客户端的connect请求!
if (newsd == -1) {
g_print("accept error!\n");
break;
}
else {
if (count >= MAX_USERS) {
sprintf(buf, "用户数量过多服务器不能通讯。\n");
write(newsd, buf, 1024);
close(newsd);
}//用户数 检测
else {
flags = fcntl(user[i].sd, F_GETFL);
fcntl(user[i].sd, F_SETFL, O_NONBLOCK);
user[count].sd = newsd;
user[count].in_use = TRUE;
read(newsd, user[count].name, 64);
g_thread_create((GThreadFunc)do_service, (gpointer)count, TRUE, NULL);
count++;
}
}
}
close(sd);
g_free(sin);
}